docs(build): row 41 slice 1 S6 round 2 candidate packet (filbert)

R1 request ids on host-broker IPC, R2 launch.sock close bound, R3 dangling
symlinks refused, notes 1-5. 42 files, +4679/-63, base 915e00e5, gate at
8dd5ff00. Candidate manifest digest fd21bdc2...eb02. Source uncommitted.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-09 20:53:06 -05:00
co-authored by Claude Opus 5.5
parent 54cb7ab8f8
commit 779e97805a
22 changed files with 1867 additions and 1263 deletions
+165 -16
View File
@@ -1,17 +1,164 @@
# Row 41 (#1523): slice 1 S6, candidate packet, round 1
# Row 41 (#1523): slice 1 S6, candidate packet, round 2
Author: Filbert. Reviewer: Darkwing. Brief: `docs/plans/2026-10-04_slice-1.md`,
section "Slice 1 S6", blob `72d11de2`. Plan: `PLAN.md` here (b6d2fe2b).
Rulings: lead decisions 77 and 78. Base: `915e00e5` (`base.txt`). None of
the 41 files changed between the base and `2855e628`, where the gate ran.
The candidate source is uncommitted. There are no pushes. No token, private
Rulings: lead decisions 77 and 78, and Sage's round 2 ruling (fix R1, R2
and R3). Base: `915e00e5` (`base.txt`). None of the 42 files changed between
the base and `8dd5ff00`, where the round 2 gate ran, or `54cb7ab8`. The
candidate source is uncommitted. There are no pushes. No token, private
binding or tracker host was read or written, and nothing touched the live
`mosaic-bus@mosaic-stack` unit or `~/.mosaic-dev/bus/`.
## Files (`files.txt`, 41)
## Round 2
Darkwing's round 1 asked for changes (#1523 comment 26995, rev 247,
packet `agents/darkwing/work/s6-review/` at `f19787ee`). Sage ruled that all three
findings are fixed in this round. R1 sits inside decision 77: it's trusted
IPC, with no socket verb, event kind or schema change. This round adds one
file, `packages/cli/tests/host.test.mjs`, which round 1 didn't touch.
### R1: host↔broker replies carry the request's id
- `host.mjs`: every request gets `id = ++seq`, and the broker process echoes
it on every launch-op, bind and startup reply (`tag()` in
`packages/bus/src/process.mjs`). A reply is taken only if its id is the
waiting request's.
- Any of these breaks the channel (`breakChannel`): no reply within
`REQUEST_TIMEOUT_MS` (10 s), a reply with another id, a reply with no id,
or a reply with no request waiting.
- Once the channel is broken:
- the waiting request refuses with `broker-channel-broken`, and so does
every later one, including those already queued;
- the host logs it and closes with exit 1, for the unit to restart;
- a launch waiting on the channel is refused, never allowed.
- Tests:
- `host.test.mjs` "a broker reply that misses the wait breaks the
channel …". The broker is SIGSTOPped with one request waiting and a
bind queued behind it. Both refuse, the broker is resumed and its late
reply answers nothing, the host exits 1, and a later request refuses.
- "a broker reply with another request's id, or none, breaks the
channel …" changes the id on the way out, so the broker echoes the
wrong one, or drops it.
- `end-launch.test.mjs` checks the echo, refusals included.
- The cli and bus READMEs say this.
Darkwing's `probe/desync.mjs` against this round: after the stall, p1 to p4
all refuse with `broker-channel-broken`, and the host logs "broker channel
broken (no reply within 10 s); stopping the host". The probe's second half
(two binds during a stall) then fails with `kill ESRCH`, because the host
has already stopped the broker. Round 1 cross-wired the replies there
(`b2 bind run-y (reviewer) got: {"run":"run-x"}`).
### R3: the gate refuses dangling symlinks
`gate.mjs` `real()` walks up with `lstat`, not `exists`, so a dangling
symlink counts as an existing name. It then takes the realpath of the
nearest existing ancestor, and an `ENOENT` from `realpathSync` means the
path reaches a dangling link. `decide()` refuses that as "path goes through
a dangling symlink", at any depth, even when the link points inside the
workspace. A write through the link would create its target wherever the
link names, and Pi's `write` creates missing parent directories first. A
link whose target exists is checked as before, so one pointing outside the
workspace is refused as outside it.
- `gate.test.mjs` "a dangling symlink is refused at any depth, in both
harnesses" covers Pi `write` and Claude Code `Write`, by relative and
absolute path. `notes.md` points outside, `dangling` points nowhere and
`inner` points to a missing name inside the workspace. All of
`notes.md`, `dangling/x`, `dangling/deep/x` and `inner` are refused. Once
the targets exist, `notes.md` is refused as outside and `inner` is
allowed. A path under a regular file is refused as `ENOTDIR`.
- `pi-session.test.mjs` "pi: a write through a dangling symlink is blocked,
and nothing appears outside" runs a real Pi session's `write` through the
extension. `notes.md` and `gone/x.md` come back as tool errors, a plain
`fine.md` lands in the workspace, and the outside directory stays empty.
- Claude Code: `--restricted` stays defence in depth. The gate refuses the
path whether or not the flag is set.
Darkwing's probes against this round:
- `pi-dangling.sh`: the dangling link gives "mosaic gate: write path goes
through a dangling symlink: notes.md", and the outside file stays absent.
With the target present, the write is refused as outside the workspace
and the file keeps its content.
- `claude-dangling.sh`: the hook refuses with the same reason, and the
outside file stays absent.
- `gate-edges.mjs`: the output matches round 1 except one line, `write
dangling/x`, which went from ALLOW to the dangling-symlink refusal.
The README's "Limits" gains a TOCTOU line: the gate checks a path when the
call is made, so a link that `bash` or another process of the same user
changes between the check and the open isn't seen. That reach is the same
as `bash` itself.
### R2: a half-open `launch.sock` client can't hold `close()`
The launcher keeps its `launch.sock` connections in a set. `close()` calls
`server.close` and then destroys each connection, before it waits for the
server to close. `launcher.test.mjs` "a launch client that never closes its
side doesn't hold the host's close" opens one client that was answered and
keeps its side open, and one that never sends. `close()` must finish within
5 s. Darkwing's `close-hang.mjs`, in both modes (`silent`, `line`), now
gives `closed 0` about 15 ms after `host.close(0)`. In round 1, the close
waited until the probe destroyed its client at 25 s. The cli README says
so.
### Notes 1 to 5
1. **`--restricted`.** The harness README and the `adapters/claude/adapter.sh`
comment now say that the flag keeps `CLAUDE.md` files (user, parent,
workspace) and auto-memory out of the prompt, and that the gate doesn't
rely on it for paths. Two tests cover it:
- "claude adapter: --restricted is always passed" runs the adapter
against a stand-in `claude` and checks argv. It runs without Claude
Code installed.
- "claude: CLAUDE.md files and auto-memory don't reach the model;
without --restricted they do" plants the four markers and runs the
real adapter against the mock API: none reaches the request. A copy of
the adapter without the flag sends all four.
Darkwing's `claude-memory.sh` gives the same result for this round: 0 of
4 with the candidate, 4 of 4 without the flag.
2. **`launchPm` skips `authorizeLaunch`.** The cli README now says so:
`launches off` doesn't stop `bus start --pm`, because the human launches
the PM, and the other launcher checks still apply.
3. **Policy parse.** `runner.mjs` reads and parses the policy inside the
`try`, so a missing or malformed policy exits 2 (usage) before the claim.
Test: "a missing or malformed policy exits 2 before the claim".
4. **System prompt in argv.** This is now a harness README "Limits" line.
No code change.
5. **Mutants.** Each new test was run against its mutant and fails. Each
source file was restored from a copy and checked with `cmp`.
| Mutant | Change | Round 1 | Round 2 |
|---|---|---|---|
| R1a | the host takes any reply while a request waits | (finding) | "another request's id, or none" fails |
| R1b | no reply timer | (finding) | the stall test fails at its 30 s test timeout instead of hanging |
| R2 | `close()` doesn't destroy connections | (finding) | the half-open test fails within its 5 s bound |
| R3 | round 1's `existsSync` walk in `real()` | (finding) | the gate and pi-session dangling tests fail |
| N1 | the adapter without `--restricted` | (note) | both claude-session tests fail |
| Mg | `founderCheck` returns null | runner test hangs | the unit test fails; the runner test fails at its 60 s clock |
| Mh | no SIGKILL after the stop timeout in `close()` | survives | "a runner that ignores SIGTERM is killed when the host closes" fails |
| Mi | no SIGKILL in `recover()` | survives | the died-hard "kills them" case fails |
| Mj | no 4096-byte stdin cap | survives | "no capability, or a malformed one, on stdin" fails |
| Mk | no `LINE_MAX` on `launch.sock` | survives | "an over-long launch request is refused at once …" fails |
How the new tests separate the mutants:
- **Mh and Mi** rely on a SIGSTOPped process, which ignores SIGTERM and
dies only on SIGKILL.
- **Mi:** in the died-hard test, the leftover session and its runner are
stopped before the next host starts. Without the stop, the runner exits
on its own once the broker is gone, which is why Mi survived round 1.
- **Mh** uses a test-only `stopTimeoutMs` (default 30000), set to 1 s.
- **Mg and Mj:** `startRunner` in `runner.test.mjs` now SIGKILLs a runner
still alive after 60 s. A regression fails there instead of hanging the
file.
- **The stall test** registers its SIGCONT hook before `host.close`. Hooks
run in order, so a stopped broker can't hold the cleanup.
## Files (`files.txt`, 42)
`build.patch` is `git diff --cached --binary 915e00e5` over those files,
+4302/−55. It applies cleanly to `2855e628` with `git apply --index`, and
+4679/−63. It applies cleanly to `8dd5ff00` with `git apply --index`, and
`sha256sum -c candidate-manifest.sha256` passes in that tree.
| Area | Files | What |
@@ -34,7 +181,8 @@ binding or tracker host was read or written, and nothing touched the live
blocks, a missing `-e` refuses to start, and a hang is bounded by the
runner's wall clock plus the `agent_end` turn marker. Claude Code uses a
command hook run as `timeout -k 2 10 <gate> || exit 2` with hook timeout
20; `--bare` is never passed. `--restricted` is defence in depth only.
20; `--bare` is never passed. `--restricted` is defence in depth for the
gate, and it keeps `CLAUDE.md` files and auto-memory out of the prompt.
Line 5 (`bash`) is the tool limit and is written as a limit.
- **The PM launches through the broker, within `launch`**: the host's
launch socket checks the instance list, that the instance isn't already
@@ -125,30 +273,31 @@ project extensions) and is untouched.
## Gate
Run at `2855e628` with `build.patch` applied, in a detached worktree,
Run at `8dd5ff00` with this round's patch applied, in a detached worktree,
sequentially, each output in `out/` (`out/summary.txt`). `TMPDIR` on the
scratch disk; `DOCKER_HOST=unix:///nonexistent.sock`, so nothing reaches
Docker.
Docker. Round 1's outputs stay in this directory at `9b670e27`.
| Suite | Pass | Fail |
|---|---|---|
| node: bus, business, cli, control-board | 74, 60, 77, 124 | 0 |
| node: conversation, discord, harness, ledger | 152, 178, 45, 78 | 0 |
| node: bus, business, cli, control-board | 74, 60, 82, 124 | 0 |
| node: conversation, discord, harness, ledger | 152, 178, 50, 78 | 0 |
| node: mosaic, queue, seat, tasks, webui | 69, 148, 27, 51, 14 | 0 |
| test-auth, conductor, config, discord | 15, 17, 24, 66 | 0 |
| test-extension-package, foundation, queue, release | 18, 44, 27, 4 | 0 |
| test-task | 26 | 2 |
Against round 1, cli gains 5 tests and harness 5.
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
unpatched base fails the same two (`out/base-test-task.txt`, identical
PASS/FAIL lines), so they are the environment, not this candidate.
An earlier gate over this candidate without the `--no-approve` edits had
`test-queue` fail F1 once ("a plain `commit -e` whose guard ran before
update-ref fails at its own HEAD update": only the stderr match). It passed
in two patched reruns, in the base run, and in this gate. See the
follow-ups below.
One edit came after the gate started: the R2 sentence in
`packages/cli/README.md` ("drops every open `launch.sock` connection …").
No suite reads that file, and it is the only file where the manifest and
the gated tree differ.
## Acceptance run: waiting
File diff suppressed because it is too large Load Diff
@@ -1,37 +1,38 @@
2a223392268c2ff798a3718ba0386335877ac998c124e48c74c264be71397c53 adapters/claude/adapter.sh
962255271e95cb30788e97cd9e86e17f384dd5f74cead435692e8b30e47af9cf adapters/pi/adapter.sh
863640ee44598a5bffd6d37d328dcddfd4bdc671f792d029ae2ee18b1055b28b adapters/README.md
8121e4e05b0559471e0d44fc0325fb08c8a883ab3db1bca451a956753cccdfe3 adapters/claude/adapter.sh
962255271e95cb30788e97cd9e86e17f384dd5f74cead435692e8b30e47af9cf adapters/pi/adapter.sh
009059ea86e1d14c5b12ed0fdad64e8cd501e19021ef6f4148a1463d55860125 docs/TOOLS.md
8cbbe58045188489932c63d4361c4b0679ae7fbf8a4a7341fc8cad43ec2b26ac packages/bus/README.md
49dc3cf603358fdbce768faaa9ebe8b5e4359a1aa813c3ffeef0d96a01d37035 packages/bus/README.md
aa71088d656455de83d9c1a42745852041ee61e2d5eb6f4c1e48e8ae29623433 packages/bus/src/broker.mjs
eb7a281746bacc65da303ff90f7bef709793c82606f177c5f21ff2005a966a3c packages/bus/src/process.mjs
0b51592777d2b035e79e2864d061615e81591bf99d43820ea9b3e52f8cf56559 packages/bus/src/process.mjs
12634ff6b6ef5b5a282bb306b30c9f02929d1fb6597e149d47d21069201399b5 packages/bus/src/runtime.mjs
1e301c58a562c7d9c1669dc657be708b17d37f26816af755894cc727653f7d6b packages/bus/tests/end-launch.test.mjs
667998fd1af37818e65b16bb515f22db73ab6077c5449367e731d16590598783 packages/cli/README.md
5b06f799e18deba2ee2eb737322f0dcfdd4a8eeae8c92e465f5acdbb894483dd packages/bus/tests/end-launch.test.mjs
e5e41c8bef670daac0507d860f7104c446c736a3897d247cbacb5ab36b440d41 packages/cli/README.md
bd207b81a2b9965b9e46da66ab31ed9a587b87e8669e8293184d4b842e45bff5 packages/cli/src/cli.mjs
9e5e08e78be214e2cfe8a1a9a0d310231d53f503e2efc1814ee00efab38fd280 packages/cli/src/host.mjs
bf7115a9404a7d3b7406d078c6be409d0d6f726d5b88496a6995ddb1c17a6fa2 packages/cli/src/launcher.mjs
e9eeec4bef3384b5b15d1e7a2c9d913d2f3e329228c3e3e2cacb8f741aa21379 packages/cli/src/host.mjs
2d0b075982f295a88161607d2795aadc86f286994ab6cc31873b83b2daebf7fa packages/cli/src/launcher.mjs
9b30a3bfe96a5d7c6956b6c75196c08bc35ceec302859337915ca1cfe2a76b76 packages/cli/tests/fixtures/launch-host.mjs
aa705a5bc9de9fa50471b5aba6217fba6c36bc2479590d88a9197c99c8c94cec packages/cli/tests/launcher.test.mjs
436c15af25c48a8135d6b4bbb7df26d318b87da32fc6432455f23043a07e791c packages/cli/tests/host.test.mjs
670e3a8dd3e57b96b96f6932693853e1149e53da577299931cbe3d428ddb89da packages/cli/tests/launcher.test.mjs
709bd331bb0d76f28b464e518f4e1fc3bb0b303614e79d7e82479dcd679043cc packages/cli/tests/verbs.test.mjs
07f8033da769b18c194520356f900dd10a12d146d4d10f1a80e3c40a2a60655e packages/harness/README.md
34ef8212e27f052223443c66830839517f7a54ecb6ef7d85795b3e89c65b4d4c packages/harness/package.json
8b3bab21b0725cb19690cde0036a6899bc608911e14446cb32139dfbbcb10c6b packages/harness/README.md
22efd0bed7991561920ad29f6bf9a1ab2d7384185fab4267684a037e026d8e85 packages/harness/src/bundle.mjs
32b5090760c95eea0e1232ff36ec13a1d9b58335b2eade621196dec1ebdbe784 packages/harness/src/claude-gate.mjs
0144ed5591d941689e8cf783daaed445ec728507d43b246cf3fffa317b0f6599 packages/harness/src/gate.mjs
570a1e64db8624b57eb972fd7599c7543e3c41324d0e9666cb5701d5491ec39b packages/harness/src/gate.mjs
71e00113b8e5b55b410c7aae6232f76e972fc77aa68afa893da45c6b78d297e2 packages/harness/src/mcp-server.mjs
d19753fa72f0b57e751749359644093713bcb650bfac566f55d2bc05cb817121 packages/harness/src/pi-extension.mjs
4065d94c84bc08f570a43071bce1955fed46503352242ebdca806e011ec624d7 packages/harness/src/runner.mjs
1047aa092080e92efde2044dddaf82bacf428ed4b143c3846693471481f3612e packages/harness/src/runner.mjs
92153d9e2161ceef4ee76f2ff992014760a8c62b0ea709b51e2ea0ce965d3edb packages/harness/src/tools.mjs
96796238b7effab78cc6356ed8ea163f02aa39999d8dfc97fef83d45309d6574 packages/harness/tests/bundle.test.mjs
96524da43b212e84d78108cbbf05eef324c934f3ad9e0d4cb2edcb0f3df256d4 packages/harness/tests/claude-gate.test.mjs
3846c68c0fda682952fe7d76e8e2006ea2d1b355516897795b4a61f55ba47ea2 packages/harness/tests/claude-session.test.mjs
197ec0f6c842552bea65fb2ab4c8cb8615fd6e691a2d0a592e1465d18a45d75f packages/harness/tests/claude-session.test.mjs
8392172b243356932c974bdca9b4c449a312ae7a042ee7a22e0f22fbb98dbec9 packages/harness/tests/fixtures/fake-adapter.mjs
df3225783a1fa35f1084a3c89b5496185901d0bb8e65f13165a5c07ea5047ee6 packages/harness/tests/gate.test.mjs
e9b0dfc28f187d58714bfdf57ec7a5f3eb19dbaa8ff6d267e19f2ad10404d073 packages/harness/tests/helpers.mjs
8576749388b3f962ed6cbd694d9af814665f3f491f278a3cdf9b9c5435388460 packages/harness/tests/gate.test.mjs
6e7509cfe6ad909440c25b750528360c0ba617c6c68b05d400bcd94b481c1f76 packages/harness/tests/helpers.mjs
793eb11f970e4a8eecddec4dc48c24331e4de795bc04bfadf806a2fc3a15a8b4 packages/harness/tests/mcp-server.test.mjs
92529a27ebac228fe2a03a12a0a2ee3b29203af4a26182a0e380a299dc327449 packages/harness/tests/pi-session.test.mjs
2fc5a3522297c16f31ced5f31f707277e18e1f860f0a6372faef3e86ea5d2ecd packages/harness/tests/runner.test.mjs
ba907e3bee4547c97c8700b4b19febc46c19074a259dd113b727522d68115273 packages/harness/tests/pi-session.test.mjs
c8f23144316501c3e163cf5a5566ddee552b3c93dee13594ae8c1eac66aedc64 packages/harness/tests/runner.test.mjs
ef9130a3cb1ca3e278a8ed370060afd1145d1ceb211a915e83d75c8346b04931 packages/harness/tests/tools.test.mjs
4e34456187387b02fa6d996c270dea4076b5d57952cddaa01f7a04843b351a02 packages/seat/README.md
382cbf7e0ff336911e288ce858bdbfbec693bc2b69879720d1f0b4c7d8455198 packages/seat/src/proc.mjs
+3 -2
View File
@@ -1,6 +1,6 @@
adapters/README.md
adapters/claude/adapter.sh
adapters/pi/adapter.sh
adapters/README.md
docs/TOOLS.md
packages/bus/README.md
packages/bus/src/broker.mjs
@@ -12,10 +12,11 @@ packages/cli/src/cli.mjs
packages/cli/src/host.mjs
packages/cli/src/launcher.mjs
packages/cli/tests/fixtures/launch-host.mjs
packages/cli/tests/host.test.mjs
packages/cli/tests/launcher.test.mjs
packages/cli/tests/verbs.test.mjs
packages/harness/package.json
packages/harness/README.md
packages/harness/package.json
packages/harness/src/bundle.mjs
packages/harness/src/claude-gate.mjs
packages/harness/src/gate.mjs
+75 -75
View File
@@ -1,77 +1,77 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (158.150768ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (258.805434ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (247.340125ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (154.366458ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (141.971144ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (131.526953ms)
✔ task action subjects and linked decision trail are complete and ordered (132.519219ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (94.302493ms)
✔ business isolation includes inherited object names and cross-business message references (143.859485ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (215.909845ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (108.67248ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (165.948325ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (103.673551ms)
✔ both arbiters require human resolution when their cross-role route is themselves (179.340595ms)
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (3.248662ms)
✔ only validated broker references load; returned data and exceptions cannot expose a known token (7.094661ms)
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (5.019312ms)
✔ expiry refuses use and env references never become client data (3.278188ms)
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (2.965278ms)
✔ opaque tokens shorter than 16 characters refuse before use (0.609186ms)
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (128.865172ms)
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (145.44321ms)
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (142.744227ms)
✔ endLaunch leaves a claim another run took alone (153.860535ms)
✔ refuse records action.refused against the caller with the code only (109.560376ms)
✔ launches off refuses role.launch with launch-revoked until launches on (158.905054ms)
✔ broker process: launch ops authorize role.launch, record refusals and end runs (203.667266ms)
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (6.466732ms)
✔ process reader gets own kernel identity without exposing environment values (1.515607ms)
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.392126ms)
✔ real pid 1 remains inspectable when its environment is protected (0.473384ms)
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (182.153792ms)
✔ missing and foreign-business citations refuse and roll back message and grant (183.462033ms)
✔ cross-role sends still need a matching resolved decision and consume it once (225.561481ms)
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (192.194069ms)
✔ startup token refusal returns safe code without value or partial listening broker (38.623009ms)
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (167.701961ms)
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (169.750172ms)
✔ trusted host registers later launches; socket clients never have a registration verb (166.888863ms)
✔ runtime excludes declared project roots even when host supplies no repoRoots (39.861791ms)
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (149.249527ms)
✔ v3b prototype refusals, views and append-only mutations (942.242629ms)
✔ gated approval authorizes once, survives store reopen, and fresh approval works (222.807767ms)
✔ another run cannot consume an approval; a failed check leaves it usable (216.443103ms)
✔ two scheduled callers have exactly one grant and one consumed refusal (149.72494ms)
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (276.865404ms)
✔ class drift gated to cross-role refuses before consumption (170.894519ms)
✔ class drift cross-role to gated refuses before consumption (158.250182ms)
✔ class drift gated to within-role refuses before consumption (177.520217ms)
✔ class drift cross-role to within-role refuses before consumption (185.093462ms)
✔ class drift within-role to gated refuses before consumption (144.027639ms)
✔ class drift within-role to cross-role refuses before consumption (152.049889ms)
✔ message.send consumes approval and prevents a later send or authorize (168.498462ms)
✔ role.revoke consumes approval and prevents a later revoke or authorize (198.374954ms)
✔ creates private WAL store and excludes a second writer until explicit close (116.738279ms)
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (175.406652ms)
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (182.907272ms)
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (154.638962ms)
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (94.948081ms)
✔ async transactions refuse before invoking their function (78.29418ms)
✔ recordTask keeps sync reads and a role write apart (155.00518ms)
✔ read_at must be one canonical UTC format, so the projection compares strings safely (97.553598ms)
✔ a bad entry refuses the whole record (103.483603ms)
✔ taskView reads the projection for one business (126.462963ms)
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (216.111452ms)
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (389.096711ms)
✔ without an adapter the server refuses every task verb (159.18258ms)
✔ the runtime refuses an invalid adapter and closes a valid one (154.938435ms)
✔ the process loads the S3 adapter from plain-data trackers (236.701945ms)
✔ socket capability stamps launch identity; shared views use wire, no SQL client (138.617926ms)
✔ two wire claims serialize; a lost reply never automatically retries (171.818149ms)
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (110.874989ms)
✔ client preserves UTF-8 when a response divides a multibyte character (11.810021ms)
✔ committed mutation followed by dropped reply reports unknown and is never retried (120.128497ms)
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (158.294055ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (259.614212ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (242.506362ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (161.976041ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (148.695884ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (137.583297ms)
✔ task action subjects and linked decision trail are complete and ordered (186.436959ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (104.399977ms)
✔ business isolation includes inherited object names and cross-business message references (188.02737ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (350.15037ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (159.818248ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (189.67006ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (124.230049ms)
✔ both arbiters require human resolution when their cross-role route is themselves (199.180591ms)
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.264919ms)
✔ only validated broker references load; returned data and exceptions cannot expose a known token (5.481319ms)
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (4.18746ms)
✔ expiry refuses use and env references never become client data (1.105304ms)
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (7.468986ms)
✔ opaque tokens shorter than 16 characters refuse before use (0.513997ms)
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (135.481002ms)
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (134.248144ms)
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (153.895653ms)
✔ endLaunch leaves a claim another run took alone (161.970249ms)
✔ refuse records action.refused against the caller with the code only (111.340973ms)
✔ launches off refuses role.launch with launch-revoked until launches on (160.281981ms)
✔ broker process: launch ops authorize role.launch, record refusals and end runs (221.362748ms)
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (3.096307ms)
✔ process reader gets own kernel identity without exposing environment values (2.657909ms)
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.273874ms)
✔ real pid 1 remains inspectable when its environment is protected (0.505849ms)
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (188.712257ms)
✔ missing and foreign-business citations refuse and roll back message and grant (177.133146ms)
✔ cross-role sends still need a matching resolved decision and consume it once (230.758224ms)
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (193.74033ms)
✔ startup token refusal returns safe code without value or partial listening broker (39.198239ms)
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (184.106087ms)
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (157.880698ms)
✔ trusted host registers later launches; socket clients never have a registration verb (173.751442ms)
✔ runtime excludes declared project roots even when host supplies no repoRoots (40.167143ms)
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (154.175551ms)
✔ v3b prototype refusals, views and append-only mutations (974.316595ms)
✔ gated approval authorizes once, survives store reopen, and fresh approval works (249.273742ms)
✔ another run cannot consume an approval; a failed check leaves it usable (219.736933ms)
✔ two scheduled callers have exactly one grant and one consumed refusal (156.511323ms)
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (273.588058ms)
✔ class drift gated to cross-role refuses before consumption (196.834803ms)
✔ class drift cross-role to gated refuses before consumption (220.857243ms)
✔ class drift gated to within-role refuses before consumption (200.437464ms)
✔ class drift cross-role to within-role refuses before consumption (298.862655ms)
✔ class drift within-role to gated refuses before consumption (232.176051ms)
✔ class drift within-role to cross-role refuses before consumption (171.660401ms)
✔ message.send consumes approval and prevents a later send or authorize (191.809221ms)
✔ role.revoke consumes approval and prevents a later revoke or authorize (196.68699ms)
✔ creates private WAL store and excludes a second writer until explicit close (116.781089ms)
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (172.437705ms)
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (169.581936ms)
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (158.747085ms)
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (99.516826ms)
✔ async transactions refuse before invoking their function (86.659243ms)
✔ recordTask keeps sync reads and a role write apart (149.917777ms)
✔ read_at must be one canonical UTC format, so the projection compares strings safely (117.042328ms)
✔ a bad entry refuses the whole record (99.401189ms)
✔ taskView reads the projection for one business (121.318568ms)
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (221.737809ms)
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (392.357601ms)
✔ without an adapter the server refuses every task verb (200.843487ms)
✔ the runtime refuses an invalid adapter and closes a valid one (213.365501ms)
✔ the process loads the S3 adapter from plain-data trackers (327.129025ms)
✔ socket capability stamps launch identity; shared views use wire, no SQL client (140.998311ms)
✔ two wire claims serialize; a lost reply never automatically retries (179.486351ms)
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (118.361562ms)
✔ client preserves UTF-8 when a response divides a multibyte character (12.083191ms)
✔ committed mutation followed by dropped reply reports unknown and is never retried (127.825313ms)
ℹ tests 74
ℹ suites 0
ℹ pass 74
@@ -79,4 +79,4 @@
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2336.232793
ℹ duration_ms 2747.495478
+61 -61
View File
@@ -1,63 +1,63 @@
✔ config directory and file path follow MOSAIC_CONFIG (1.341178ms)
✔ the fixture business validates and comes back frozen (4.26895ms)
✔ two instances may share a definition (1.580704ms)
✔ top-level refusals (4.501169ms)
✔ arbiters and projects (5.4681ms)
✔ role instances (3.487746ms)
✔ Vikunja bots (8.02749ms)
✔ a role without Vikunja takes no tracker block (10.757229ms)
✔ credential references match the definition's services (3.823637ms)
✔ launch (9.590847ms)
✔ loadBusiness: file checks (1.975951ms)
✔ loadBusiness: not a regular file (41.201527ms)
✔ loading writes nothing (1.314092ms)
✔ names that are Object.prototype properties don't count as declared (2.899494ms)
✔ the shipped example refuses as written and validates once filled in (0.575228ms)
✔ usage errors exit 4 (318.00225ms)
✔ validate: a good business exits 0 and prints instance digests (68.995958ms)
✔ validate: project files (349.686854ms)
✔ validate: missing files and a broken system config (326.343919ms)
✔ validate: credential reference problems exit 2 and name each one (99.678089ms)
✔ validate: a token file inside the repository is refused (78.247664ms)
✔ validate: role definitions come from MOSAIC_ROLES_DIR (216.675669ms)
✔ resolve: prints one instance's record (248.880265ms)
✔ resolve: refusals (412.874057ms)
✔ parse: exactly one of file or env, plus the service's date (2.325764ms)
✔ check: a good file has no problems (0.75954ms)
✔ check never opens the file: a write-only token passes (0.311868ms)
✔ check: file problems (0.763239ms)
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (0.798484ms)
✔ check: dates and environment references (0.361131ms)
✔ path and load (2.885491ms)
✔ refusals (1.515385ms)
✔ systemVars flattens the validated config (1.790585ms)
✔ precedence: system, business, project, project role, agent (4.547817ms)
✔ limits narrow the definition and never widen it (2.16752ms)
✔ role.launch stays within-role only for the instance the launch block names (4.55638ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (1.912786ms)
✔ limits.authority narrows cross-role actions too (1.129226ms)
✔ classify (1.062031ms)
✔ the record carries what the broker and launcher need (9.084401ms)
✔ digest: key order doesn't matter, any value change does (7.179908ms)
✔ refusals (2.455766ms)
✔ the four shipped version 2 roles load (3.959226ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.430244ms)
✔ shipped authority follows the note's table (0.81314ms)
✔ version 1 files keep loading with no authority (1.244032ms)
✔ the conductor policy isn't a role (0.262325ms)
✔ a missing role file is exit 4, a symbolic link too (0.547733ms)
✔ version 2 refusals (1.428674ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (2.035327ms)
✔ credentials: Gitea scopes (0.825672ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.075392ms)
✔ credentials: services (8.42904ms)
✔ contract: a non-empty regular Markdown file beside the role file (0.830731ms)
✔ every key names known layers and a merge rule (0.926442ms)
✔ unknown keys and wrong layers refuse (0.796639ms)
✔ types (1.829487ms)
✔ merge: defaults, then the most specific layer wins (0.289155ms)
✔ merge: limits only narrow, and provenance lists each source (0.406369ms)
✔ merge doesn't change its inputs (0.149857ms)
✔ config directory and file path follow MOSAIC_CONFIG (1.91162ms)
✔ the fixture business validates and comes back frozen (5.712812ms)
✔ two instances may share a definition (1.68694ms)
✔ top-level refusals (4.925076ms)
✔ arbiters and projects (6.586166ms)
✔ role instances (3.076648ms)
✔ Vikunja bots (6.665056ms)
✔ a role without Vikunja takes no tracker block (2.871312ms)
✔ credential references match the definition's services (2.642532ms)
✔ launch (7.613634ms)
✔ loadBusiness: file checks (2.052319ms)
✔ loadBusiness: not a regular file (47.666198ms)
✔ loading writes nothing (1.520534ms)
✔ names that are Object.prototype properties don't count as declared (2.954491ms)
✔ the shipped example refuses as written and validates once filled in (0.63865ms)
✔ usage errors exit 4 (322.723133ms)
✔ validate: a good business exits 0 and prints instance digests (71.239074ms)
✔ validate: project files (341.689159ms)
✔ validate: missing files and a broken system config (291.858814ms)
✔ validate: credential reference problems exit 2 and name each one (75.189953ms)
✔ validate: a token file inside the repository is refused (74.77167ms)
✔ validate: role definitions come from MOSAIC_ROLES_DIR (222.784057ms)
✔ resolve: prints one instance's record (227.709604ms)
✔ resolve: refusals (443.985563ms)
✔ parse: exactly one of file or env, plus the service's date (2.456548ms)
✔ check: a good file has no problems (0.775399ms)
✔ check never opens the file: a write-only token passes (0.407183ms)
✔ check: file problems (0.843938ms)
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (0.861362ms)
✔ check: dates and environment references (0.465771ms)
✔ path and load (2.010503ms)
✔ refusals (1.064579ms)
✔ systemVars flattens the validated config (2.461642ms)
✔ precedence: system, business, project, project role, agent (8.241305ms)
✔ limits narrow the definition and never widen it (2.836179ms)
✔ role.launch stays within-role only for the instance the launch block names (5.636504ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (2.024129ms)
✔ limits.authority narrows cross-role actions too (1.025935ms)
✔ classify (1.912616ms)
✔ the record carries what the broker and launcher need (0.981644ms)
✔ digest: key order doesn't matter, any value change does (6.433678ms)
✔ refusals (2.701053ms)
✔ the four shipped version 2 roles load (3.348744ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.219132ms)
✔ shipped authority follows the note's table (0.64651ms)
✔ version 1 files keep loading with no authority (1.080549ms)
✔ the conductor policy isn't a role (0.255061ms)
✔ a missing role file is exit 4, a symbolic link too (0.438644ms)
✔ version 2 refusals (1.487183ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (2.444188ms)
✔ credentials: Gitea scopes (1.019814ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.419399ms)
✔ credentials: services (0.596546ms)
✔ contract: a non-empty regular Markdown file beside the role file (0.836011ms)
✔ every key names known layers and a merge rule (0.986663ms)
✔ unknown keys and wrong layers refuse (0.879891ms)
✔ types (2.321956ms)
✔ merge: defaults, then the most specific layer wins (0.418729ms)
✔ merge: limits only narrow, and provenance lists each source (0.426782ms)
✔ merge doesn't change its inputs (0.189432ms)
ℹ tests 60
ℹ suites 0
ℹ pass 60
@@ -65,4 +65,4 @@
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2187.474485
ℹ duration_ms 2141.835239
+85 -80
View File
@@ -1,87 +1,92 @@
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (118.155092ms)
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (135.340975ms)
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (110.707482ms)
✔ decide prints a declining choice as declining (92.798251ms)
✔ an unknown outcome is reported once and never resent (87.28131ms)
✔ a decision closed before the answer arrives exits 2 and points at its trail (95.98171ms)
✔ a prefix that matches two open decisions exits 2 and resolves neither (88.223671ms)
✔ without --business a command uses the live host's business, and a stale host.json is not a host (62.710471ms)
✔ every human command refuses inside an agent run before it touches the bus (61.875025ms)
✔ usage errors exit 4; no business and no host is a usage error (65.175578ms)
✔ agents and tasks print through the broker (68.785667ms)
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.467569ms)
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (59.037059ms)
✔ trackers come from the tracker.* variables of the one project that names a tracker project (43.777779ms)
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (50.148848ms)
✔ two projects that each name a tracker project refuse, since the boot shape holds one (53.581893ms)
✔ a business without tracker.baseUrl gets no trackers entry (39.300001ms)
✔ an unknown business and a broken system config refuse with exit 3 (83.383681ms)
✔ empty views say so (1.181626ms)
✔ the trail keeps the broker's order and names a decision's task without its rows (1.366794ms)
✔ tasks print the tracker fields the snapshot carries (0.251098ms)
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (974.34103ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (212.356371ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (128.8785ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (222.476083ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (176.961283ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (135.640371ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (198.923159ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (117.161338ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (220.743628ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (22.783177ms)
✔ bus stop refuses to signal a live pid that is not a bus host (202.440829ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (259.625833ms)
✔ bus start refuses with exit 3 without a notifier config (99.337871ms)
✔ bus start runs until bus stop; status reports it while it runs (723.184797ms)
✔ bus-service.sh renders the unit and installs it into a given directory (40.188497ms)
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1298.529746ms)
✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (648.981918ms)
✔ a runner that stops at once ends its launch with the runner's reason (218.756086ms)
✔ 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 (718.902837ms)
✔ 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 (3649.636207ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (133.892391ms)
✔ zoned uses the IANA zone across DST (20.415121ms)
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (116.186459ms)
✔ two blocking decisions get two DMs with different nonces (120.670182ms)
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.445474ms)
✔ a failed DM is journaled, backs off, and is retried until it lands (120.593616ms)
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (101.787488ms)
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (97.104103ms)
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (92.253269ms)
✔ 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 (163.676718ms)
✔ a restart after the second refusal does not send before that refusal's 30 min are up (110.941061ms)
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (87.725819ms)
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (63.461429ms)
✔ an inbox read failure is logged and the next poll retries (0.580971ms)
✔ no Discord id reaches the journal or the log (72.972195ms)
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (11.00011ms)
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (16.377097ms)
✔ the journal: a whole file that is one torn line truncates to empty (21.36201ms)
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.604157ms)
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.56872ms)
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.811585ms)
✔ the journal: a symlinked directory refuses and says it is a link (0.322853ms)
✔ 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.464501ms)
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.375347ms)
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.438934ms)
✔ digest content stays within Discord's 2000 characters (0.353809ms)
✔ runLoop never overlaps ticks and stops after the one in flight (111.16002ms)
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (146.776928ms)
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (136.801344ms)
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (103.086176ms)
✔ decide prints a declining choice as declining (134.723723ms)
✔ an unknown outcome is reported once and never resent (118.430821ms)
✔ a decision closed before the answer arrives exits 2 and points at its trail (96.582082ms)
✔ a prefix that matches two open decisions exits 2 and resolves neither (93.910913ms)
✔ without --business a command uses the live host's business, and a stale host.json is not a host (100.191586ms)
✔ every human command refuses inside an agent run before it touches the bus (89.223907ms)
✔ usage errors exit 4; no business and no host is a usage error (83.466371ms)
✔ agents and tasks print through the broker (99.638395ms)
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.56895ms)
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (53.987196ms)
✔ trackers come from the tracker.* variables of the one project that names a tracker project (50.54766ms)
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (52.575457ms)
✔ two projects that each name a tracker project refuse, since the boot shape holds one (41.939509ms)
✔ a business without tracker.baseUrl gets no trackers entry (44.220059ms)
✔ an unknown business and a broken system config refuse with exit 3 (77.545035ms)
✔ empty views say so (1.213263ms)
✔ the trail keeps the broker's order and names a decision's task without its rows (1.465349ms)
✔ tasks print the tracker fields the snapshot carries (0.259502ms)
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (968.120835ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (244.370802ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (195.501934ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1168.798507ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (252.111853ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (192.327382ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (149.865957ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (109.163695ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (152.634134ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (116.928022ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (185.169842ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (26.156512ms)
✔ bus stop refuses to signal a live pid that is not a bus host (203.06222ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (218.54117ms)
✔ bus start refuses with exit 3 without a notifier config (87.417832ms)
✔ bus start runs until bus stop; status reports it while it runs (665.256061ms)
✔ bus-service.sh renders the unit and installs it into a given directory (27.206795ms)
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1344.798577ms)
✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (619.298083ms)
✔ a runner that stops at once ends its launch with the runner's reason (199.689244ms)
✔ 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 (651.083095ms)
✔ 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 (3599.486558ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (100.745764ms)
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (115.983641ms)
✔ a launch client that never closes its side doesn't hold the host's close (138.95718ms)
✔ a runner that ignores SIGTERM is killed when the host closes (1228.324814ms)
✔ zoned uses the IANA zone across DST (19.841055ms)
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (152.572961ms)
✔ two blocking decisions get two DMs with different nonces (127.82059ms)
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.211354ms)
✔ a failed DM is journaled, backs off, and is retried until it lands (112.394683ms)
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (138.735105ms)
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (129.023835ms)
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (102.098533ms)
✔ 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 (172.839344ms)
✔ a restart after the second refusal does not send before that refusal's 30 min are up (125.552318ms)
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (112.654074ms)
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (75.674926ms)
✔ an inbox read failure is logged and the next poll retries (0.621548ms)
✔ no Discord id reaches the journal or the log (86.296393ms)
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (41.163659ms)
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (55.929482ms)
✔ the journal: a whole file that is one torn line truncates to empty (24.260055ms)
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (3.363193ms)
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.952902ms)
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (3.077859ms)
✔ the journal: a symlinked directory refuses and says it is a link (0.589086ms)
✔ 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.754225ms)
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.464759ms)
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.527358ms)
✔ digest content stays within Discord's 2000 characters (0.352942ms)
✔ runLoop never overlaps ticks and stops after the one in flight (111.100812ms)
task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200
task.close on a missing task answered: task-not-found; it made GET /tasks/999 404
✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (444.699888ms)
✔ the transport writes {business, verb, args} to the child and reads its JSON (47.864309ms)
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2212.180406ms)
✔ busExit and refuseInsideAgent (0.532377ms)
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (210.726131ms)
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1134.898685ms)
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (98.253828ms)
✔ launches off and on go to the broker and change the business's launch state (87.660028ms)
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (73.549533ms)
ℹ tests 77
✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (450.705988ms)
✔ the transport writes {business, verb, args} to the child and reads its JSON (46.868792ms)
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2232.458816ms)
✔ busExit and refuseInsideAgent (0.506966ms)
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (213.349559ms)
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1113.591923ms)
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (116.819293ms)
✔ launches off and on go to the broker and change the business's launch state (109.402112ms)
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (64.2915ms)
ℹ tests 82
ℹ suites 0
ℹ pass 77
ℹ pass 82
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 6786.010947
ℹ duration_ms 8105.490386
+125 -125
View File
@@ -1,127 +1,127 @@
✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (4.641886ms)
✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.268969ms)
✔ completed smoke replies and ordinary questions are idle, not human blockers (1.020682ms)
✔ only an explicit first-line input request makes a finished reply waiting (0.289824ms)
✔ tool activity, user text, errors and unfinished turns override attention text (0.167445ms)
✔ STOP access failure is unknown, not absence, under a non-root identity (54.374685ms)
✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (3.166721ms)
✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (1.667541ms)
✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (6.805422ms)
✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.669073ms)
✔ server rescans connector discovery and refuses HTTP reply without transport (43.145184ms)
✔ connector session links and linked directories are not read (1.27055ms)
✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (3.270946ms)
✔ CLI print uses the relaunch notice instead of old current preview (68.257724ms)
✔ connector owner and fixed task never inherit a native relaunch notice (3.060282ms)
✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.70917ms)
✔ loadConfig: missing file throws ConfigError (1.787365ms)
✔ loadConfig: invalid JSON throws ConfigError (0.347612ms)
✔ loadConfig: missing dataRoot throws ConfigError (0.259123ms)
✔ loadConfig: relative dataRoot throws ConfigError (0.243686ms)
✔ loadConfig: valid config returns dataRoot (0.798268ms)
✔ findNewestSession: picks the newest by mtime among two files (0.526496ms)
✔ findNewestSession: finds files in nested subdirectories (0.406065ms)
✔ findNewestSession: returns null for a missing dir (0.137243ms)
✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.770616ms)
✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.401954ms)
✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.536856ms)
✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (1.449147ms)
✔ deriveState: full state table (0.200445ms)
✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.407812ms)
✔ rule: newest entry is a tool result with no assistant text after it is working (0.324887ms)
✔ rule: a finished ordinary turn is idle, even if it says your move (0.34233ms)
✔ task: the first user message of the session, from text blocks (0.364454ms)
✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.306066ms)
✔ task: no user message in the log means null (shown as unknown), never a guess (0.341252ms)
✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.442806ms)
✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.797736ms)
✔ scan: the written record carries task, workspace and activeProject (0.634047ms)
✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.683352ms)
✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.458167ms)
✔ 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.138285ms)
✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.509043ms)
✔ loadRegistrations: a missing seatsDir gives empty lists (0.193861ms)
✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (1.188153ms)
✔ 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.37717ms)
✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (1.460457ms)
✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.322685ms)
✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.671017ms)
✔ scanAgent: ageSeconds is computed from the injected now (0.324056ms)
✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.192104ms)
✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.429286ms)
✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.42239ms)
✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (1.202797ms)
✔ scan: relative boardDir throws ConfigError (0.116583ms)
✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (75.904446ms)
✔ CLI: missing config exits 2 with a refused: message (68.3446ms)
✔ CLI: unknown command exits 2 (60.818611ms)
✔ CLI: unknown --liveness value exits 2 (62.922692ms)
✔ panesRunPi: true when any trimmed line equals 'pi' (0.277749ms)
✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.110431ms)
✔ tmuxIsAlive: a pane running pi is alive (0.261019ms)
✔ tmuxIsAlive: session exists but pi has exited is not alive (0.088258ms)
✔ tmuxIsAlive: no such tmux session is not alive (0.054366ms)
✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.074409ms)
✔ tmuxIsAlive: passes -L <socket> only when a socket is given (0.100801ms)
✔ parsePanes: one pane per line, command and optional tab-separated path (0.101551ms)
✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.123154ms)
✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.170557ms)
✔ loadSeen: missing file returns {} (0.209322ms)
✔ loadSeen: invalid JSON throws ConfigError (0.415243ms)
✔ loadSeen: a JSON array throws ConfigError (0.211211ms)
✔ loadSeen: a non-string value throws ConfigError (0.231069ms)
✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.40028ms)
✔ markSeen: seen false deletes the key (0.283061ms)
✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.238733ms)
✔ markSeen: project containing '/' throws ConfigError (0.134177ms)
✔ markSeen: non-boolean seen throws ConfigError (0.123902ms)
✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.320389ms)
✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.270898ms)
✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.244607ms)
✔ scanAgent: an error-state session with a matching mark is seen (0.257963ms)
✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.568729ms)
✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.20305ms)
✔ 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.726555ms)
✔ 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.645998ms)
✔ taskSetBy: scan() reads the field from disk through the seat package (bounded there), writes it to the per-agent record and index, and an invalid on-disk value is a registrationError, never a row value (1.045846ms)
✔ isLoopbackHost: recognizes loopback hosts (1.386893ms)
✔ isLoopbackHost: rejects non-loopback hosts (4.71065ms)
✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (3.447209ms)
✔ startServer: serves page, healthz, and a rescanning /api/board (40.64407ms)
✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (7.221166ms)
✔ startServer: /api/board returns 500 JSON with an error field when scan throws (3.361978ms)
✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (72.261995ms)
✔ CLI: serve rejects a non-numeric --port with exit 2 (59.137725ms)
✔ CLI: scan still works after the async cli refactor (64.194896ms)
✔ CLI: live serve prints its URL and answers /healthz (70.924896ms)
✔ page.html: esc() escapes every HTML-significant character (0.796687ms)
✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (9.921107ms)
✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (2.365807ms)
✔ POST /api/seen with invalid JSON returns 400 (3.525478ms)
✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (2.617466ms)
✔ POST /api/seen with a missing agent returns 400 (1.613299ms)
✔ POST /api/board returns 405; PUT /api/seen returns 405 (2.065397ms)
✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (54.790079ms)
✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.36576ms)
✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.343455ms)
✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.206124ms)
✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.160804ms)
✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.345793ms)
✔ 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.628983ms)
✔ 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 (34.931804ms)
✔ POST /api/reply: a registration with a tmux socket adds -L <socket> (35.233392ms)
✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (45.23328ms)
✔ 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 (22.970179ms)
✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (6.70926ms)
✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.244726ms)
✔ 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.703156ms)
✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (4.279429ms)
✔ 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.433195ms)
✔ 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 (11.556202ms)
✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (33.618842ms)
✔ conversation routes (F16): a foreign Host, a wrong port and a cross-origin Origin get 403 before the reader runs, with no CORS headers (6.280378ms)
✔ every refusal code the reader can raise has an HTTP status (1.042543ms)
✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (54.030753ms)
✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (3.691168ms)
✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.238072ms)
✔ completed smoke replies and ordinary questions are idle, not human blockers (0.714916ms)
✔ only an explicit first-line input request makes a finished reply waiting (0.167776ms)
✔ tool activity, user text, errors and unfinished turns override attention text (0.129623ms)
✔ STOP access failure is unknown, not absence, under a non-root identity (33.013191ms)
✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (3.180599ms)
✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (1.38538ms)
✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (6.11372ms)
✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.622748ms)
✔ server rescans connector discovery and refuses HTTP reply without transport (33.252474ms)
✔ connector session links and linked directories are not read (0.975739ms)
✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (2.6993ms)
✔ CLI print uses the relaunch notice instead of old current preview (62.64595ms)
✔ connector owner and fixed task never inherit a native relaunch notice (2.453627ms)
✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.69238ms)
✔ loadConfig: missing file throws ConfigError (1.813512ms)
✔ loadConfig: invalid JSON throws ConfigError (0.320534ms)
✔ loadConfig: missing dataRoot throws ConfigError (0.241769ms)
✔ loadConfig: relative dataRoot throws ConfigError (0.243223ms)
✔ loadConfig: valid config returns dataRoot (0.694324ms)
✔ findNewestSession: picks the newest by mtime among two files (0.606778ms)
✔ findNewestSession: finds files in nested subdirectories (0.449554ms)
✔ findNewestSession: returns null for a missing dir (0.190029ms)
✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.953037ms)
✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.571058ms)
✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.503874ms)
✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (1.243797ms)
✔ deriveState: full state table (0.285701ms)
✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.375156ms)
✔ rule: newest entry is a tool result with no assistant text after it is working (0.333717ms)
✔ rule: a finished ordinary turn is idle, even if it says your move (0.278815ms)
✔ task: the first user message of the session, from text blocks (0.299581ms)
✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.272129ms)
✔ task: no user message in the log means null (shown as unknown), never a guess (0.302093ms)
✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.433808ms)
✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.574491ms)
✔ scan: the written record carries task, workspace and activeProject (0.567433ms)
✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.655101ms)
✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.43647ms)
✔ 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.087099ms)
✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.468892ms)
✔ loadRegistrations: a missing seatsDir gives empty lists (0.171266ms)
✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (0.848083ms)
✔ 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.378216ms)
✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (1.248013ms)
✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.252124ms)
✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.603413ms)
✔ scanAgent: ageSeconds is computed from the injected now (0.2699ms)
✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.170176ms)
✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.37218ms)
✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.378987ms)
✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (0.967412ms)
✔ scan: relative boardDir throws ConfigError (0.093777ms)
✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (58.802027ms)
✔ CLI: missing config exits 2 with a refused: message (51.021156ms)
✔ CLI: unknown command exits 2 (50.285259ms)
✔ CLI: unknown --liveness value exits 2 (55.257373ms)
✔ panesRunPi: true when any trimmed line equals 'pi' (0.247684ms)
✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.078529ms)
✔ tmuxIsAlive: a pane running pi is alive (0.211692ms)
✔ tmuxIsAlive: session exists but pi has exited is not alive (0.076486ms)
✔ tmuxIsAlive: no such tmux session is not alive (0.061456ms)
✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.062541ms)
✔ tmuxIsAlive: passes -L <socket> only when a socket is given (0.096803ms)
✔ parsePanes: one pane per line, command and optional tab-separated path (0.07962ms)
✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.074285ms)
✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.097511ms)
✔ loadSeen: missing file returns {} (0.155968ms)
✔ loadSeen: invalid JSON throws ConfigError (0.219738ms)
✔ loadSeen: a JSON array throws ConfigError (0.168137ms)
✔ loadSeen: a non-string value throws ConfigError (0.168774ms)
✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.309188ms)
✔ markSeen: seen false deletes the key (0.24856ms)
✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.218582ms)
✔ markSeen: project containing '/' throws ConfigError (0.111987ms)
✔ markSeen: non-boolean seen throws ConfigError (0.108609ms)
✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.282144ms)
✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.247828ms)
✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.227325ms)
✔ scanAgent: an error-state session with a matching mark is seen (0.227839ms)
✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.496047ms)
✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.186552ms)
✔ 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.619757ms)
✔ 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.563664ms)
✔ 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.939906ms)
✔ isLoopbackHost: recognizes loopback hosts (1.277363ms)
✔ isLoopbackHost: rejects non-loopback hosts (4.523177ms)
✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (3.59353ms)
✔ startServer: serves page, healthz, and a rescanning /api/board (35.659466ms)
✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (6.146856ms)
✔ startServer: /api/board returns 500 JSON with an error field when scan throws (2.400245ms)
✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (53.880169ms)
✔ CLI: serve rejects a non-numeric --port with exit 2 (47.763257ms)
✔ CLI: scan still works after the async cli refactor (53.502565ms)
✔ CLI: live serve prints its URL and answers /healthz (59.412096ms)
✔ page.html: esc() escapes every HTML-significant character (0.648508ms)
✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (7.799646ms)
✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (2.508134ms)
✔ POST /api/seen with invalid JSON returns 400 (1.903236ms)
✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (1.975783ms)
✔ POST /api/seen with a missing agent returns 400 (1.259383ms)
✔ POST /api/board returns 405; PUT /api/seen returns 405 (1.573435ms)
✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (50.607954ms)
✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.317323ms)
✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.260037ms)
✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.154397ms)
✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.16282ms)
✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.283516ms)
✔ 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.548275ms)
✔ 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 (28.520776ms)
✔ POST /api/reply: a registration with a tmux socket adds -L <socket> (27.006224ms)
✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (27.012188ms)
✔ POST /api/reply: refusals before the tool runs: empty or blank or long text 400, unknown row 404, no registration 409, stale registration 409, no tmux session 409, bad JSON 400; the tool is never called (13.925126ms)
✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (4.242121ms)
✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.14168ms)
✔ 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.571395ms)
✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (2.507482ms)
✔ 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.311159ms)
✔ 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.759398ms)
✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (31.693895ms)
✔ 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.606208ms)
✔ every refusal code the reader can raise has an HTTP status (0.955939ms)
✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (45.78585ms)
ℹ tests 124
ℹ suites 0
ℹ pass 124
@@ -129,4 +129,4 @@
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 774.560773
ℹ duration_ms 625.036405
+153 -153
View File
@@ -1,155 +1,155 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (366.928224ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (39.093165ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (22.903774ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (542.049518ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (504.802304ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (322.66265ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (265.106214ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (240.449712ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.957749ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (12666.098171ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (30953.87984ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (266.87467ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (145.296641ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (410.27827ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (282.414513ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (352.278489ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (119.390385ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (185.414736ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (368.009297ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (813.857743ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (185.527387ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (89.437752ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (72.840588ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.634554ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.002053ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.819507ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3075.872051ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (550.816208ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2938.878855ms)
✔ K4: two engines; force stop one; the other survives by independent observation (5462.103605ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2612.388068ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2982.619477ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2432.457076ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5115.442166ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (761.540049ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (672.791692ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (615.943344ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (879.881573ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (417.30095ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (585.898732ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3559.770078ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (28.327037ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (451.142569ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (461.907538ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (352.559273ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (260.467726ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (246.161026ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (338.624679ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (240.108119ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.597623ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (224.445395ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (365.969368ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (254.094418ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (226.861015ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (223.090316ms)
✔ 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 (280.697687ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.533788ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (257.59532ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (295.516735ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (312.122245ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (255.96624ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.511256ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.298392ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.366575ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.137018ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (313.570237ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (334.013214ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (339.086815ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (963.27244ms)
✔ H4: self-takeover is refused (238.984643ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (489.013009ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1350.016488ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (277.044896ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (903.141931ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (381.724817ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (216.486779ms)
✔ H13: a retry with the same request ID and different text is refused (245.581951ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (729.986176ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (650.617325ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (277.608467ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (1252.558084ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (1188.412704ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (271.516375ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (396.880958ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.859705ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (724.938928ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (798.741876ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (925.243776ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2958.527643ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (700.652019ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.875614ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.974279ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.412892ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (3.648476ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.996645ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.144196ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.973217ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.342077ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (4.217095ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.361039ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.856576ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.198373ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (8.623027ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (9.408628ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (2.534355ms)
✔ F9: registrations never add or redirect a root (1.900641ms)
✔ F10: a header cwd naming another project is refused (5.34781ms)
✔ F11: parentSession renders with a marker and the parent is never opened (1.297027ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (5.378605ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.400711ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.357901ms)
✔ F13: compaction is a marker in place, then the retained content (0.791328ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (869.46465ms)
✔ fragments never cut a surrogate pair and keep an empty string (5.322608ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.694545ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.993734ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.835641ms)
✔ a seat directory without search permission refuses that root, not the catalogue (3.409315ms)
✔ every page and cursor is a valid CHAT-01 record (1171.522693ms)
✔ the engine pin holds for the installed package (2.969351ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (339.716856ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (465.727325ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (689.187817ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (514.977427ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (292.410254ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (23.468859ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (443.221347ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (247.308821ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (322.736492ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (551.870499ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1961.565503ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (244.212591ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (290.90596ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (246.059572ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (289.589211ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (501.30469ms)
✔ N10: fake conformance (34.023867ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (831.090384ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (361.055636ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (358.002753ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (589.637419ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (581.06703ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (562.073768ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (264.022573ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (462.86689ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (860.27459ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (580.591412ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (480.465854ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (339.574789ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (338.952874ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (233.893576ms)
✔ 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 (240.739111ms)
✔ W1: two processes acquire the same pair at once; exactly one claim (500.172302ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (34.447301ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (21.245207ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (380.782492ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (415.536314ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (258.443009ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (208.321924ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (209.096493ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.343681ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (10639.643125ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (33722.598575ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (297.831509ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (167.189405ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (484.422997ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (317.977402ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (434.213183ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (138.169018ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (202.378983ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (397.75689ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (946.3831ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (225.315133ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (93.363585ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (72.203607ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.569722ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.325889ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.738078ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2794.484922ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (469.89623ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2644.635443ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4852.089312ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2563.661152ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2494.802302ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2746.623948ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5278.616647ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (920.216881ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (750.409927ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (590.614603ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (984.081218ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (486.910482ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (679.003936ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3412.938836ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (36.405575ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (486.630344ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (531.704758ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (504.380237ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (217.876841ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (212.438677ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (308.695737ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (205.567788ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.474109ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (213.418843ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (304.548788ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (230.67903ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (232.279238ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (225.911236ms)
✔ 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 (226.928276ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.157214ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (240.351129ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (230.11528ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (317.639113ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (374.398052ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.539984ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.282556ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.30821ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.123271ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (245.322895ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (559.299151ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (306.639949ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (864.367817ms)
✔ H4: self-takeover is refused (207.977962ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (489.967376ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1162.284778ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (400.286574ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (854.487718ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (474.571479ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (235.94039ms)
✔ H13: a retry with the same request ID and different text is refused (212.137643ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (722.250079ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (301.681102ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (230.452513ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (939.093746ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (959.856778ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (363.959262ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (409.106014ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.655539ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (703.309217ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (603.731272ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (790.922524ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2967.111484ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (1706.924017ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (7.943027ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.772943ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.752425ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.556892ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.541133ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.990715ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (1.130976ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.328783ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (4.148949ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (7.152193ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (6.687027ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.6262ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.512257ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.779647ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (2.496262ms)
✔ F9: registrations never add or redirect a root (1.588804ms)
✔ F10: a header cwd naming another project is refused (3.361116ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.776124ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (5.470116ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.272352ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.350252ms)
✔ F13: compaction is a marker in place, then the retained content (0.727293ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (523.566064ms)
✔ fragments never cut a surrogate pair and keep an empty string (5.179993ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.677094ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.369772ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.093831ms)
✔ a seat directory without search permission refuses that root, not the catalogue (2.100106ms)
✔ every page and cursor is a valid CHAT-01 record (804.092641ms)
✔ the engine pin holds for the installed package (1.919918ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (331.021752ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (275.832025ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (806.068343ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (469.189878ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (254.60826ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.156872ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (368.884282ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (213.556408ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (331.026384ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (537.207506ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1925.400277ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (258.018644ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (378.641567ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (210.25368ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (309.741586ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (446.67773ms)
✔ N10: fake conformance (31.971148ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (474.454081ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (300.784606ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (324.956671ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (414.997271ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (450.246043ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (562.546304ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (274.511468ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (398.931267ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (919.910026ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (662.641784ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (582.515503ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (203.649783ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (225.882597ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (213.819475ms)
✔ 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 (229.585286ms)
ℹ tests 152
ℹ suites 0
ℹ pass 152
@@ -157,4 +157,4 @@
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 51185.439363
ℹ duration_ms 52102.666518
+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.30556ms)
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.570005ms)
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.647254ms)
✔ approvals: a button approves only on its own request message with the matching custom id (0.487432ms)
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (17.371847ms)
✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (9.276715ms)
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.081808ms)
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.09649ms)
✔ authorize: open channel, listed user (3.676715ms)
✔ authorize: wrong guild (0.208021ms)
✔ authorize: no guild (DM) (0.188467ms)
✔ authorize: unlisted channel (0.18996ms)
✔ authorize: unknown channel, no info (0.40522ms)
✔ authorize: thread of listed parent (0.205975ms)
✔ authorize: thread of unlisted parent (0.173754ms)
✔ authorize: text channel that is not a thread and not listed (0.145406ms)
✔ authorize: unlisted user (0.174784ms)
✔ authorize: no author (0.408525ms)
✔ authorize: bot author (listed id, bot flag) (0.129557ms)
✔ authorize: system author (0.168624ms)
✔ authorize: the bot itself (0.099418ms)
✔ authorize: webhook (0.09274ms)
✔ authorize: mention channel without mention (0.117129ms)
✔ authorize: mention channel with bot mention (0.133164ms)
✔ authorize: mention channel with @everyone only (0.094295ms)
✔ authorize: mention channel mentioning someone else (0.074797ms)
✔ authorize: mention channel, content says @bot but mentions empty (0.094055ms)
✔ authorize: private thread under mention channel, mentioned (0.0797ms)
✔ authorize: private thread under mention channel, not mentioned (0.068534ms)
✔ authorize: thread in another guild per channel info (0.069749ms)
✔ authorize: not an object (0.063066ms)
✔ authorize: no id (2.317016ms)
✔ authorize: oversize content is accepted and flagged (0.100512ms)
✔ authorize: exactly the limit is not oversize (0.06151ms)
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.514873ms)
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.162041ms)
✔ binding: a complete binding validates and is frozen (2.678019ms)
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.292634ms)
✔ binding: empty allowlists refuse (0.395772ms)
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.460534ms)
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.072326ms)
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.829546ms)
✔ binding: file must be 0600, regular, not a symlink (3.695384ms)
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (3.125574ms)
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (89.43539ms)
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.535478ms)
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (501.87896ms)
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (351.757699ms)
✔ cli: run refuses when STOP is present, before any network use (290.165911ms)
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (3.754926ms)
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.998397ms)
✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (18.999258ms)
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.07216ms)
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.420386ms)
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.100597ms)
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.177542ms)
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.756928ms)
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.594306ms)
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.043242ms)
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.119759ms)
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (0.915495ms)
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.686556ms)
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.574885ms)
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.382548ms)
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.44967ms)
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.296315ms)
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.264071ms)
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.449145ms)
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.790787ms)
✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (3.599111ms)
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.515266ms)
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.616309ms)
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.647835ms)
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.331026ms)
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.548305ms)
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.806956ms)
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.861674ms)
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.359756ms)
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.52254ms)
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.414285ms)
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.647646ms)
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.406205ms)
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.407002ms)
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (62.210504ms)
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (41.029075ms)
✔ engine: one prompt, one turn, text and usage come back (53.820075ms)
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (354.233703ms)
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (264.785114ms)
✔ engine: timeout sends abort and fails only that turn; the process stays (121.168917ms)
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (277.171717ms)
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (158.997315ms)
✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (215.616932ms)
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.286838ms)
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.417736ms)
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.455254ms)
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.744815ms)
✔ engine: a malformed JSONL line fails the turn, not the process (25.344053ms)
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.663653ms)
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.462075ms)
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.806422ms)
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.633551ms)
✔ gateway: op 9 resumable resumes (0.539501ms)
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.784361ms)
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.708919ms)
✔ gateway: close() is final and unparseable frames are ignored (0.627142ms)
✔ git: config validation is strict, needs write: true, a work tree and a private token file (58.774185ms)
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (41.09462ms)
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (128.590736ms)
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.476101ms)
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (127.537922ms)
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (221.213798ms)
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (236.019056ms)
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (487.643115ms)
✔ git: push pushes the named branch only and reports up to date (91.790307ms)
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (93.185619ms)
✔ git: the credential helper answers get over https from a private file and nothing else (216.017392ms)
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (737.247015ms)
✔ lock: the claim is exclusive; a second start against a live owner refuses (5.952379ms)
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (75.442042ms)
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (0.726436ms)
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (2.710958ms)
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (62.258522ms)
✔ 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 (476.231867ms)
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.810492ms)
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (49.81495ms)
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (4.254546ms)
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (98.186867ms)
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (261.39297ms)
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (189.602729ms)
✔ notices: a kind is recorded per UTC day and found again (0.872726ms)
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (4.954714ms)
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (1.924999ms)
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.597964ms)
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (38.82067ms)
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (36.200206ms)
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (53.157519ms)
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (100.977523ms)
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (1154.269076ms)
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.410433ms)
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.954227ms)
✔ rest: content and nonce limits are enforced locally; typing never throws (1.166406ms)
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.771431ms)
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.876233ms)
✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.974428ms)
✔ setspark config: reaches the tools config and the binding as a fixed key (2.950589ms)
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.409577ms)
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (1.61629ms)
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (19.615454ms)
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (7.461312ms)
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (5.264184ms)
✔ setspark keys: read per call, one printable token per file, rotation without a restart (2.464963ms)
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.491046ms)
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.075832ms)
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1016.278029ms)
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.852283ms)
✔ 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 (31.796702ms)
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (6.636421ms)
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.474284ms)
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (9.083242ms)
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.662943ms)
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.042075ms)
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.050779ms)
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (2.896622ms)
✔ tools: listing and search caps hold (10.266894ms)
✔ tools: credential shapes are caught; ordinary prose and ids are not (0.853382ms)
✔ 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.582953ms)
✔ tools: an unreadable file under the root is skipped by search and refused by read (0.987122ms)
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.046852ms)
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (4.759934ms)
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.765593ms)
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.666132ms)
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (2.483424ms)
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.90685ms)
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1024.220555ms)
✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (15.168569ms)
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.539613ms)
✔ 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 (13.220746ms)
✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (6.399067ms)
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.513067ms)
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.622039ms)
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.657851ms)
✔ approvals: a button approves only on its own request message with the matching custom id (0.506829ms)
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (20.372481ms)
✔ 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.942082ms)
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (5.686265ms)
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.451802ms)
✔ authorize: open channel, listed user (2.089988ms)
✔ authorize: wrong guild (0.204754ms)
✔ authorize: no guild (DM) (0.17918ms)
✔ authorize: unlisted channel (2.484597ms)
✔ authorize: unknown channel, no info (0.252987ms)
✔ authorize: thread of listed parent (0.213716ms)
✔ authorize: thread of unlisted parent (0.155577ms)
✔ authorize: text channel that is not a thread and not listed (0.331012ms)
✔ authorize: unlisted user (0.195206ms)
✔ authorize: no author (0.352216ms)
✔ authorize: bot author (listed id, bot flag) (0.171847ms)
✔ authorize: system author (0.11868ms)
✔ authorize: the bot itself (0.100113ms)
✔ authorize: webhook (0.091549ms)
✔ authorize: mention channel without mention (0.139131ms)
✔ authorize: mention channel with bot mention (0.269928ms)
✔ authorize: mention channel with @everyone only (0.110887ms)
✔ authorize: mention channel mentioning someone else (0.074523ms)
✔ authorize: mention channel, content says @bot but mentions empty (0.089763ms)
✔ authorize: private thread under mention channel, mentioned (0.092374ms)
✔ authorize: private thread under mention channel, not mentioned (0.064862ms)
✔ authorize: thread in another guild per channel info (0.076909ms)
✔ authorize: not an object (0.063716ms)
✔ authorize: no id (0.066689ms)
✔ authorize: oversize content is accepted and flagged (0.077499ms)
✔ authorize: exactly the limit is not oversize (0.058051ms)
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.495786ms)
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (1.552985ms)
✔ binding: a complete binding validates and is frozen (2.984441ms)
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.82498ms)
✔ binding: empty allowlists refuse (0.367254ms)
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.571886ms)
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.058133ms)
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.492391ms)
✔ binding: file must be 0600, regular, not a symlink (3.49713ms)
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (2.149593ms)
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (110.567998ms)
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.40013ms)
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (349.708865ms)
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (216.498377ms)
✔ cli: run refuses when STOP is present, before any network use (137.183231ms)
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.049555ms)
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.221218ms)
✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (17.53611ms)
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.196582ms)
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.487369ms)
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.174302ms)
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.223402ms)
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.155626ms)
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (31.706556ms)
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.981659ms)
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (0.826213ms)
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (0.655156ms)
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (41.645133ms)
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.315913ms)
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (5.881895ms)
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.712336ms)
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (1.233407ms)
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (3.998497ms)
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.241541ms)
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.496718ms)
✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (5.570856ms)
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.101271ms)
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.133321ms)
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.142423ms)
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.321246ms)
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.391083ms)
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.782881ms)
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.023176ms)
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.253659ms)
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.584064ms)
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.262211ms)
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.94795ms)
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (4.050497ms)
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.467297ms)
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (65.861693ms)
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (42.322062ms)
✔ engine: one prompt, one turn, text and usage come back (35.874086ms)
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (350.609136ms)
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (240.28076ms)
✔ engine: timeout sends abort and fails only that turn; the process stays (106.320108ms)
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (232.398024ms)
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (125.558786ms)
✔ 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.642182ms)
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.22918ms)
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (2.158478ms)
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.938839ms)
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (431.707736ms)
✔ engine: a malformed JSONL line fails the turn, not the process (27.92475ms)
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.568294ms)
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (3.962931ms)
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.820945ms)
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.505877ms)
✔ gateway: op 9 resumable resumes (0.388086ms)
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.880231ms)
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.577018ms)
✔ gateway: close() is final and unparseable frames are ignored (0.433901ms)
✔ git: config validation is strict, needs write: true, a work tree and a private token file (79.813399ms)
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (47.008635ms)
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (75.188574ms)
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.289055ms)
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (101.080883ms)
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (105.441377ms)
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (94.002264ms)
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (234.268712ms)
✔ git: push pushes the named branch only and reports up to date (74.775987ms)
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (98.386955ms)
✔ git: the credential helper answers get over https from a private file and nothing else (211.796826ms)
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (832.162327ms)
✔ lock: the claim is exclusive; a second start against a live owner refuses (4.805541ms)
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (91.35218ms)
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.026631ms)
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (5.010642ms)
✔ 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.081716ms)
✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (335.097304ms)
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.478148ms)
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (26.306592ms)
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.148782ms)
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (44.742055ms)
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (151.909677ms)
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (91.508358ms)
✔ notices: a kind is recorded per UTC day and found again (0.663793ms)
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.350295ms)
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.184953ms)
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.79695ms)
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (47.744741ms)
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (48.327362ms)
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (35.166632ms)
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (77.466581ms)
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (736.615945ms)
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (4.774151ms)
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.44002ms)
✔ rest: content and nonce limits are enforced locally; typing never throws (1.936608ms)
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (1.290834ms)
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.845933ms)
✔ setspark config: a bare https or loopback origin, a private key file, a principal (4.723533ms)
✔ setspark config: reaches the tools config and the binding as a fixed key (3.019049ms)
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.727043ms)
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.039368ms)
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (23.379469ms)
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (11.751887ms)
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (6.042864ms)
✔ setspark keys: read per call, one printable token per file, rotation without a restart (2.676422ms)
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.46837ms)
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (2.002481ms)
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1011.153004ms)
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.47554ms)
✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (7.378494ms)
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.8349ms)
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.306603ms)
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.854819ms)
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.013354ms)
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.014583ms)
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.436845ms)
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.615438ms)
✔ tools: listing and search caps hold (9.65926ms)
✔ tools: credential shapes are caught; ordinary prose and ids are not (0.903078ms)
✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (6.27101ms)
✔ tools: an unreadable file under the root is skipped by search and refused by read (1.332638ms)
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.474367ms)
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.680721ms)
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.018418ms)
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.491755ms)
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.852318ms)
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.335601ms)
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1027.404183ms)
✔ 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.231804ms)
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.2477ms)
✔ 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.610441ms)
✔ 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.220307ms)
ℹ tests 178
ℹ suites 0
ℹ pass 178
@@ -183,4 +183,4 @@
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2785.874422
ℹ duration_ms 2687.487226
+53 -48
View File
@@ -1,53 +1,58 @@
✔ sessionModel: agent vars win, then the system's execution settings (9.863443ms)
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (4.904843ms)
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.507558ms)
✔ a bundle is written once: an existing file refuses (2.342637ms)
✔ a path with a single quote can't go into the hook command (2.404071ms)
✔ allow exits 0, a deny exits 2 with the reason on stderr (119.133617ms)
✔ a missing or wrong policy, or a bad event, exits 2 (89.62829ms)
✔ the bundle's wrapped command: a missing gate or node still blocks (1090.433581ms)
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (764.522718ms)
✔ claude: the hook alone blocks a path outside the workspace (429.718725ms)
✔ claude: a second turn resumes the first turn's session (697.342346ms)
✔ claude: a missing hook or MCP file refuses before claude starts (7.462303ms)
✔ pi: policy tools and typed tools pass, anything else is blocked (3.652974ms)
✔ claude: builtins map from pi names, typed tools need the mcp prefix (1.053779ms)
✔ file tool paths must resolve inside the workspace (1.386549ms)
✔ pi's own path normalisation can't be used to step out (1.267113ms)
✔ a symlink inside the workspace that points out is outside (1.233345ms)
✔ claude path fields per tool (1.085399ms)
✔ glob patterns stay inside the workspace (1.257359ms)
✔ a path that can't be checked is blocked (0.780884ms)
✔ initialize, ping and tools/list (45.612153ms)
✔ tools/call goes through the tool socket; a refusal is an isError result (35.942819ms)
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (36.869003ms)
✔ a missing argument is a usage error (31.167661ms)
✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (362.276951ms)
✔ pi: a missing extension refuses before any model call (7.449552ms)
✔ pi: an extension without its configuration fails pi's start (260.274424ms)
✔ founderCheck: founder variables, then a needed service without a usable token (1.668322ms)
✔ turnRequest names the sender, class, reply and decision (0.273454ms)
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (245.265643ms)
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (105.218887ms)
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (395.154351ms)
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1288.799719ms)
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (150.812622ms)
✔ founder credentials stop before the claim (20) (163.622892ms)
✔ a refused claim exits 21; an ended run's capability exits 22 (193.590834ms)
✔ the launch ending under a running session exits 22 (142.09675ms)
✔ a broker that stays unreachable exits 23 after brokerRetries polls (280.860796ms)
✔ a broker that is down at the claim exits 23, not 21 (88.821778ms)
✔ no capability, or a malformed one, on stdin exits 2 (155.374009ms)
✔ the PM gets launch, its task verbs and the reads (9.166295ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.866852ms)
✔ launch only when the business's launch block names the instance as launcher (2.365004ms)
✔ an action outside the instance's authority has no tool (2.33749ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (8.138127ms)
ℹ tests 45
✔ sessionModel: agent vars win, then the system's execution settings (13.13889ms)
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (4.754143ms)
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.793955ms)
✔ a bundle is written once: an existing file refuses (2.706625ms)
✔ a path with a single quote can't go into the hook command (1.81122ms)
✔ allow exits 0, a deny exits 2 with the reason on stderr (124.816491ms)
✔ a missing or wrong policy, or a bad event, exits 2 (95.807838ms)
✔ the bundle's wrapped command: a missing gate or node still blocks (1096.785958ms)
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (851.292573ms)
✔ claude: the hook alone blocks a path outside the workspace (519.27064ms)
✔ claude: a second turn resumes the first turn's session (776.321586ms)
✔ claude adapter: --restricted is always passed (5.499034ms)
✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (867.702037ms)
✔ claude: a missing hook or MCP file refuses before claude starts (10.298703ms)
✔ pi: policy tools and typed tools pass, anything else is blocked (4.220059ms)
✔ claude: builtins map from pi names, typed tools need the mcp prefix (1.062326ms)
✔ file tool paths must resolve inside the workspace (1.522023ms)
✔ pi's own path normalisation can't be used to step out (1.483331ms)
✔ a symlink inside the workspace that points out is outside (1.269628ms)
✔ a dangling symlink is refused at any depth, in both harnesses (4.263112ms)
✔ claude path fields per tool (1.118926ms)
✔ glob patterns stay inside the workspace (1.022964ms)
✔ a path that can't be checked is blocked (0.454162ms)
✔ initialize, ping and tools/list (48.504759ms)
✔ tools/call goes through the tool socket; a refusal is an isError result (38.124036ms)
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (33.297546ms)
✔ a missing argument is a usage error (30.763266ms)
✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (405.108517ms)
✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (381.475986ms)
✔ pi: a missing extension refuses before any model call (7.159081ms)
✔ pi: an extension without its configuration fails pi's start (340.423518ms)
✔ founderCheck: founder variables, then a needed service without a usable token (1.358533ms)
✔ turnRequest names the sender, class, reply and decision (0.199896ms)
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (249.9341ms)
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (116.889992ms)
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (423.548366ms)
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1349.631237ms)
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (177.09152ms)
✔ founder credentials stop before the claim (20) (194.175258ms)
✔ a refused claim exits 21; an ended run's capability exits 22 (236.143025ms)
✔ the launch ending under a running session exits 22 (118.270606ms)
✔ a broker that stays unreachable exits 23 after brokerRetries polls (250.227078ms)
✔ a broker that is down at the claim exits 23, not 21 (93.191317ms)
✔ no capability, or a malformed one, on stdin exits 2 (189.733487ms)
✔ a missing or malformed policy exits 2 before the claim (128.184915ms)
✔ the PM gets launch, its task verbs and the reads (6.94313ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.622772ms)
✔ launch only when the business's launch block names the instance as launcher (2.472817ms)
✔ an action outside the instance's authority has no tool (2.525087ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (10.34312ms)
ℹ tests 50
ℹ suites 0
ℹ pass 45
ℹ pass 50
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 10300.527757
ℹ duration_ms 10459.373798
+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.436722ms)
✔ the raw path accepts nothing else, and refuses before curl runs (377.041391ms)
✔ the file checks still apply on the raw path: mode, symlink, missing, directory (304.201805ms)
✔ the raw path base URL has no override (66.337482ms)
✔ the JSON path is unchanged, and JSON never falls through to the raw path (253.125376ms)
✔ a file that changes between the two reads refuses before curl runs, with or without a body (761.646883ms)
✔ the token reaches no child environment, even with an inherited CFG or SHELLOPTS=allexport (687.660023ms)
✔ real helper GET HTTP 200 preserves exit 0 without credentials (14.229594ms)
✔ real helper POST HTTP 201 preserves exit 0 without credentials (10.603237ms)
✔ real helper GET HTTP 403 preserves exit 1 without credentials (8.102968ms)
✔ fixture git subjects only, follow-ups and three session kinds (167.878949ms)
✔ text and JSON carry same numbers, open and truncated title (229.73911ms)
✔ missing credentials exit 2, no-issues never calls API and shows unknown (196.523617ms)
✔ empty range gives no rows and zero totals (162.150955ms)
✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (162.339817ms)
✔ close-only issue included, even median, missing metadata stays unknown (136.236958ms)
✔ unique commits but per-issue links count multiple tags once each (153.608813ms)
✔ page cap refuses rather than silently undercounting (190.21276ms)
✔ bad API payload not JSON refuses (138.853987ms)
✔ bad API payload {} refuses (136.396639ms)
✔ bad API payload [{"number":1}] refuses (128.488652ms)
✔ partial or malformed session log refuses with location, not content (148.218826ms)
✔ a U+2028 or U+2029 inside a session string is one line, not a malformed record (152.576029ms)
✔ no sessions is an empty table; symlink source refuses (213.349624ms)
✔ reads only refactor even when another branch is checked out (158.481962ms)
✔ invalid dates, reverse dates and duplicate options refuse (113.994757ms)
✔ T3 agent assignments do not count as human in Table 2 (144.192731ms)
✔ preamble parsing and issue number boundaries (0.467113ms)
✔ T3 header: agent, or board from control-board; anything short of the full header is human (0.151781ms)
✔ no closed issues with human messages means undefined ratio, not invented zero (0.195161ms)
✔ T3: seat, archived, unmapped and Researcher threads count; imported, deleted and other-project threads do not (488.437473ms)
✔ T3: the default path is read from HOME and prints no path line; --no-t3 says so (762.579521ms)
✔ T3: a HOME with no database exits 1 and names --no-t3 (138.909365ms)
✔ T3: a file that is not a database exits 1 and names --no-t3 (168.711684ms)
✔ T3: a seat thread renamed to another seat exits 1 naming thread, title and roles (450.231489ms)
✔ T3: an unmapped thread addressed as a seat exits 1 (443.080389ms)
✔ T3: a header to another thread id is not cross-checked (443.796967ms)
✔ T3: no project, or two, for this root exits 1 (980.381993ms)
✔ T3: a removed column exits 1 and names it (258.690726ms)
✔ T3: a missing table exits 1 and names it (259.346622ms)
✔ T3: a counted row with an unknown role exits 1 without its text (404.02286ms)
✔ T3: a counted row with non-text content exits 1 without its text (474.680664ms)
✔ T3: a counted row with an unparseable created_at exits 1 without its text (705.621194ms)
✔ T3: a missing orchestration_events makes the diagnostic unknown and keeps the counts (776.458741ms)
✔ T3: a human message with no event counts in humanWithoutEvent (400.28293ms)
✔ T3: an unparseable event makes the diagnostic unknown and keeps the counts (712.325593ms)
✔ T3: an event with no string messageId makes the diagnostic unknown and keeps the counts (720.348958ms)
✔ T3: an error that is not from SQLite is rethrown, not reported as a database failure (349.418685ms)
✔ T3: a symlink at ~/.t3 exits 1 (146.908862ms)
✔ T3: a symlink at ~/.t3/userdata exits 1 (135.470026ms)
✔ T3: a symlink at ~/.t3/userdata/state.sqlite exits 1 (138.959173ms)
✔ T3: with --t3-db, a symlinked file or directory exits 1 (459.255718ms)
✔ T3 WAL: the newest message only in -wal, writer attached, is counted (397.757105ms)
✔ T3 WAL: stopped cleanly, counts are correct and the main file is unchanged (394.598399ms)
✔ T3 WAL: -wal without -shm in a writable directory is read (421.291307ms)
✔ T3 WAL: -wal without -shm in a read-only directory exits 1 (414.513572ms)
✔ T3 WAL: stopped cleanly in a read-only directory exits 1 (407.146353ms)
✔ T3: a lock held past the 5 s busy timeout exits 1 and names --no-t3 (5413.589411ms)
✔ a done row whose closing issue is open is a violation; a row that is not done is not (1.794885ms)
✔ an issue several rows close is expected closed only once all of them are done (0.499267ms)
✔ closure needs positive evidence: unknown is undecided, and so is a skipped or short issue check (0.358303ms)
✔ each owner of an in-progress or in-review row gets one liveness class (7.492986ms)
✔ a required row not done after 14 days is a violation; a legacy row uses genesis as its lower bound (0.357012ms)
✔ an ISO requiredSince, as `set required` writes it, ages from its UTC day; one that does not parse is a violation (0.22952ms)
✔ the text section always ends in a count and a result, and never prints a full pass (0.318585ms)
✔ pidAlive: a running pid is present, an exited one is gone, and EPERM still means present (30.752549ms)
✔ issue states: open list first, then the metric page, then at most 10 lookups (282.692701ms)
✔ a full open list: lookups settle what it leaves out, and only an unsettled issue keeps it undecided (402.628517ms)
✔ the open list refuses on a failed call or a bad record, and never echoes the helper (198.012007ms)
✔ a helper call past the deadline is killed with its child, and the call reports it (2010.177468ms)
✔ readQueue loads queue.json through the queue validator and refuses anything else (151.117396ms)
✔ protected changes list every in-range entry that changes a required or parked row (448.554587ms)
✔ the CLI prints the queue section above the weekly table and under a queue key in --json (551.263507ms)
✔ a queue with nothing wrong prints 0 violations and a reduced pass, never a full pass (271.834681ms)
✔ --no-issues makes no call and leaves the issue checks undecided; --no-queue skips the section (340.015635ms)
✔ the CLI refuses a bad queue before any call, and a failed open list with exit 2 (302.404168ms)
✔ --unsupported-runtime repeats once per seat and takes a seat name (368.468703ms)
✔ an unreadable config makes every owner invalid instead of passing them (194.237873ms)
✔ a raw token file, with or without one trailing newline, reaches curl only through the config stream (115.944145ms)
✔ the raw path accepts nothing else, and refuses before curl runs (435.681779ms)
✔ the file checks still apply on the raw path: mode, symlink, missing, directory (292.596759ms)
✔ the raw path base URL has no override (57.417954ms)
✔ the JSON path is unchanged, and JSON never falls through to the raw path (241.09635ms)
✔ a file that changes between the two reads refuses before curl runs, with or without a body (716.736353ms)
✔ the token reaches no child environment, even with an inherited CFG or SHELLOPTS=allexport (785.126238ms)
✔ real helper GET HTTP 200 preserves exit 0 without credentials (13.102534ms)
✔ real helper POST HTTP 201 preserves exit 0 without credentials (9.478963ms)
✔ real helper GET HTTP 403 preserves exit 1 without credentials (8.609799ms)
✔ fixture git subjects only, follow-ups and three session kinds (161.732019ms)
✔ text and JSON carry same numbers, open and truncated title (283.864137ms)
✔ missing credentials exit 2, no-issues never calls API and shows unknown (203.728119ms)
✔ empty range gives no rows and zero totals (173.519667ms)
✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (160.627786ms)
✔ close-only issue included, even median, missing metadata stays unknown (146.587403ms)
✔ unique commits but per-issue links count multiple tags once each (149.63893ms)
✔ page cap refuses rather than silently undercounting (155.481838ms)
✔ bad API payload not JSON refuses (133.857006ms)
✔ bad API payload {} refuses (135.827145ms)
✔ bad API payload [{"number":1}] refuses (138.744231ms)
✔ partial or malformed session log refuses with location, not content (160.62317ms)
✔ a U+2028 or U+2029 inside a session string is one line, not a malformed record (151.198757ms)
✔ no sessions is an empty table; symlink source refuses (242.064585ms)
✔ reads only refactor even when another branch is checked out (174.678374ms)
✔ invalid dates, reverse dates and duplicate options refuse (115.749728ms)
✔ T3 agent assignments do not count as human in Table 2 (151.662023ms)
✔ preamble parsing and issue number boundaries (0.467437ms)
✔ T3 header: agent, or board from control-board; anything short of the full header is human (0.152354ms)
✔ no closed issues with human messages means undefined ratio, not invented zero (0.198078ms)
✔ T3: seat, archived, unmapped and Researcher threads count; imported, deleted and other-project threads do not (557.126543ms)
✔ T3: the default path is read from HOME and prints no path line; --no-t3 says so (718.686387ms)
✔ T3: a HOME with no database exits 1 and names --no-t3 (163.535756ms)
✔ T3: a file that is not a database exits 1 and names --no-t3 (142.692882ms)
✔ T3: a seat thread renamed to another seat exits 1 naming thread, title and roles (418.551026ms)
✔ T3: an unmapped thread addressed as a seat exits 1 (460.458843ms)
✔ T3: a header to another thread id is not cross-checked (446.050289ms)
✔ T3: no project, or two, for this root exits 1 (1228.151017ms)
✔ T3: a removed column exits 1 and names it (263.155213ms)
✔ T3: a missing table exits 1 and names it (250.866899ms)
✔ T3: a counted row with an unknown role exits 1 without its text (378.791672ms)
✔ T3: a counted row with non-text content exits 1 without its text (487.567915ms)
✔ T3: a counted row with an unparseable created_at exits 1 without its text (402.431871ms)
✔ T3: a missing orchestration_events makes the diagnostic unknown and keeps the counts (698.410145ms)
✔ T3: a human message with no event counts in humanWithoutEvent (434.282761ms)
✔ T3: an unparseable event makes the diagnostic unknown and keeps the counts (725.856377ms)
✔ T3: an event with no string messageId makes the diagnostic unknown and keeps the counts (709.744542ms)
✔ T3: an error that is not from SQLite is rethrown, not reported as a database failure (349.43309ms)
✔ T3: a symlink at ~/.t3 exits 1 (148.50403ms)
✔ T3: a symlink at ~/.t3/userdata exits 1 (172.909616ms)
✔ T3: a symlink at ~/.t3/userdata/state.sqlite exits 1 (178.900103ms)
✔ T3: with --t3-db, a symlinked file or directory exits 1 (586.925199ms)
✔ T3 WAL: the newest message only in -wal, writer attached, is counted (473.441354ms)
✔ T3 WAL: stopped cleanly, counts are correct and the main file is unchanged (481.222531ms)
✔ T3 WAL: -wal without -shm in a writable directory is read (522.716681ms)
✔ T3 WAL: -wal without -shm in a read-only directory exits 1 (489.764562ms)
✔ T3 WAL: stopped cleanly in a read-only directory exits 1 (389.568508ms)
✔ T3: a lock held past the 5 s busy timeout exits 1 and names --no-t3 (5415.405848ms)
✔ a done row whose closing issue is open is a violation; a row that is not done is not (1.763922ms)
✔ an issue several rows close is expected closed only once all of them are done (0.477384ms)
✔ closure needs positive evidence: unknown is undecided, and so is a skipped or short issue check (0.353983ms)
✔ each owner of an in-progress or in-review row gets one liveness class (7.516547ms)
✔ a required row not done after 14 days is a violation; a legacy row uses genesis as its lower bound (0.386933ms)
✔ an ISO requiredSince, as `set required` writes it, ages from its UTC day; one that does not parse is a violation (0.229823ms)
✔ the text section always ends in a count and a result, and never prints a full pass (0.335678ms)
✔ pidAlive: a running pid is present, an exited one is gone, and EPERM still means present (22.228761ms)
✔ issue states: open list first, then the metric page, then at most 10 lookups (314.974468ms)
✔ a full open list: lookups settle what it leaves out, and only an unsettled issue keeps it undecided (413.703487ms)
✔ the open list refuses on a failed call or a bad record, and never echoes the helper (171.870124ms)
✔ a helper call past the deadline is killed with its child, and the call reports it (2008.175883ms)
✔ readQueue loads queue.json through the queue validator and refuses anything else (153.594153ms)
✔ protected changes list every in-range entry that changes a required or parked row (407.661757ms)
✔ the CLI prints the queue section above the weekly table and under a queue key in --json (555.885026ms)
✔ a queue with nothing wrong prints 0 violations and a reduced pass, never a full pass (261.24945ms)
✔ --no-issues makes no call and leaves the issue checks undecided; --no-queue skips the section (308.534593ms)
✔ the CLI refuses a bad queue before any call, and a failed open list with exit 2 (294.675823ms)
✔ --unsupported-runtime repeats once per seat and takes a seat name (395.674304ms)
✔ an unreadable config makes every owner invalid instead of passing them (195.182174ms)
ℹ tests 78
ℹ suites 0
ℹ pass 78
@@ -83,4 +83,4 @@
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 20056.827353
ℹ duration_ms 20587.431008
+70 -70
View File
@@ -1,72 +1,72 @@
✔ pure resolution selects current default or explicit enrolled account (1.985324ms)
✔ scope is explicit, bounded and never inferred (1.73224ms)
✔ fork pin is preserved against default change, override, missing account and revocation (0.773137ms)
✔ unenrolled account/provider, missing harness, model expansion and native model ceiling refuse (0.816764ms)
✔ only explicit synthetic credential forms and internal fixture stores admitted (6.000185ms)
✔ two concurrent workspaces of the same agent publish distinct complete private generations (66.448108ms)
✔ same execution ID is exclusively claimed and cannot overwrite a published generation (40.55271ms)
✔ failed generation after-auth preserves prior files, records failure and refuses blind same-ID retry (49.434978ms)
✔ failed generation before-publish preserves prior files, records failure and refuses blind same-ID retry (59.888007ms)
✔ credential lock contention refuses without duplicate side effects (12.175935ms)
✔ symlinked pre-existing final target is refused and never followed (28.367262ms)
✔ invalid registry cannot resolve; no fallback to supplied partial entries (0.309415ms)
✔ post-publication failure records uncertainty, preserves complete generation and prevents replay (28.285567ms)
✔ expired credentials refresh under transaction and subsequent generation reuses rotation (90.362553ms)
✔ refresh failure retains prior generation and store state (65.024119ms)
✔ refresh timeout retains prior generation and store state (147.617882ms)
✔ refresh malformed retains prior generation and store state (72.635364ms)
✔ concurrent refresh on same account refuses contention while unrelated account proceeds (224.359729ms)
✔ invalid refresh options refuse before burning claim (32.821353ms)
✔ fixed fake process rotates both OAuth fields without mutating caller input (35.083253ms)
✔ concurrent isolated processes preserve separate provider credentials (35.340301ms)
✔ fake failure is refused with fixed diagnostics (28.741764ms)
✔ fake malformed is refused with fixed diagnostics (25.676151ms)
✔ fake timeout is refused with fixed diagnostics (104.470792ms)
✔ fake unchanged is refused with fixed diagnostics (29.116894ms)
✔ caller executable/environment injection is rejected before spawning (0.338944ms)
✔ valid fixture tree validates and lists without secrets (91.216095ms)
✔ unknown-field refuses (0.431311ms)
✔ invalid-id refuses uppercase and traversal shapes (0.296856ms)
✔ plain-http baseUrl requires allowInsecureTransport (0.309826ms)
✔ native provider rejects allowInsecureTransport (0.13606ms)
✔ unsupported credential type and kind refuse (0.154604ms)
✔ account provider-path mismatch refuses (0.112673ms)
✔ profile account refs must be provider/account shaped (0.273262ms)
✔ seat selection accepts fork pin field, validates account refs (0.323896ms)
✔ harness manifest id must equal executable (gate 1) (0.257206ms)
✔ CLI validate: duplicate provider id across files refuses (35.850199ms)
✔ CLI validate: missing referenced provider/account refuse (39.95611ms)
✔ CLI validate: broken JSON refuses without secret echo (33.756201ms)
✔ CLI usage errors exit 2 (60.031617ms)
✔ credential.json sibling presence does not break validation and is never read (70.398482ms)
✔ D1 missing, empty and structurally empty roots refuse, no list projection (210.576539ms)
✔ D1 required directory auth cannot be absent (55.85294ms)
✔ D1 required directory auth/providers cannot be absent (60.124101ms)
✔ D1 required directory auth/accounts cannot be absent (75.645966ms)
✔ D1 required directory auth/settings cannot be absent (63.682272ms)
✔ D1 required directory harnesses cannot be absent (63.842306ms)
✔ D1 root file and unreadable metadata refuse (109.803932ms)
✔ D2 no symlink traversal at auth/providers/openai-codex.json (72.57022ms)
✔ D2 no symlink traversal at auth/accounts/openai-codex/homelab-openai (64.503262ms)
✔ D2 no symlink traversal at auth/providers (55.265618ms)
✔ D2 no symlink traversal at auth (58.05731ms)
✔ D2 root and ancestor symlinks and lexical traversal refuse (167.655041ms)
✔ private filesystem modes enforced for root (56.250121ms)
✔ private filesystem modes enforced for auth (56.308938ms)
✔ private filesystem modes enforced for auth/providers/openai-codex.json (66.924543ms)
✔ private filesystem modes enforced for auth/accounts/openai-codex/homelab-openai/account.json (63.934499ms)
✔ D3 numeric version 1 only across all record kinds (1.260244ms)
✔ D4 nested unknown keys and missing per-kind required fields refuse (69.021863ms)
✔ D5 unenrolled default refuses even when account exists (64.812827ms)
✔ D6 provider/account credential type must match (62.987986ms)
✔ D7 every harness endpoint enforces HTTP opt-in and shape (0.472439ms)
✔ D8 URLs reject embedded credentials and unsupported protocols without echo (173.260503ms)
✔ D9 malformed JSON diagnostics contain no content excerpt (63.240916ms)
✔ D10 missing metadata is missing-path, not invalid-json (61.83338ms)
✔ D10 library returns no partial entries on any invalid record (76.193682ms)
✔ null/scalar/array metadata refuses without stack or echo (226.566502ms)
✔ credential sibling is never opened, even when an unreadable symlink (31.456806ms)
✔ oversized metadata refuses before parsing (62.953789ms)
✔ pure resolution selects current default or explicit enrolled account (1.878586ms)
✔ scope is explicit, bounded and never inferred (1.581008ms)
✔ fork pin is preserved against default change, override, missing account and revocation (0.716452ms)
✔ unenrolled account/provider, missing harness, model expansion and native model ceiling refuse (0.776018ms)
✔ only explicit synthetic credential forms and internal fixture stores admitted (5.536742ms)
✔ two concurrent workspaces of the same agent publish distinct complete private generations (39.166081ms)
✔ same execution ID is exclusively claimed and cannot overwrite a published generation (32.875243ms)
✔ failed generation after-auth preserves prior files, records failure and refuses blind same-ID retry (45.958981ms)
✔ failed generation before-publish preserves prior files, records failure and refuses blind same-ID retry (56.130026ms)
✔ credential lock contention refuses without duplicate side effects (11.675666ms)
✔ symlinked pre-existing final target is refused and never followed (30.62088ms)
✔ invalid registry cannot resolve; no fallback to supplied partial entries (0.276916ms)
✔ post-publication failure records uncertainty, preserves complete generation and prevents replay (26.435512ms)
✔ expired credentials refresh under transaction and subsequent generation reuses rotation (84.153218ms)
✔ refresh failure retains prior generation and store state (64.937175ms)
✔ refresh timeout retains prior generation and store state (144.305597ms)
✔ refresh malformed retains prior generation and store state (65.747822ms)
✔ concurrent refresh on same account refuses contention while unrelated account proceeds (215.395134ms)
✔ invalid refresh options refuse before burning claim (37.333871ms)
✔ fixed fake process rotates both OAuth fields without mutating caller input (30.930125ms)
✔ concurrent isolated processes preserve separate provider credentials (31.4959ms)
✔ fake failure is refused with fixed diagnostics (24.940459ms)
✔ fake malformed is refused with fixed diagnostics (25.340709ms)
✔ fake timeout is refused with fixed diagnostics (104.107324ms)
✔ fake unchanged is refused with fixed diagnostics (23.534776ms)
✔ caller executable/environment injection is rejected before spawning (0.284482ms)
✔ valid fixture tree validates and lists without secrets (66.88632ms)
✔ unknown-field refuses (0.382128ms)
✔ invalid-id refuses uppercase and traversal shapes (0.275639ms)
✔ plain-http baseUrl requires allowInsecureTransport (0.296326ms)
✔ native provider rejects allowInsecureTransport (0.125085ms)
✔ unsupported credential type and kind refuse (0.153165ms)
✔ account provider-path mismatch refuses (0.100346ms)
✔ profile account refs must be provider/account shaped (0.214059ms)
✔ seat selection accepts fork pin field, validates account refs (0.645005ms)
✔ harness manifest id must equal executable (gate 1) (0.201294ms)
✔ CLI validate: duplicate provider id across files refuses (29.663789ms)
✔ CLI validate: missing referenced provider/account refuse (32.571833ms)
✔ CLI validate: broken JSON refuses without secret echo (30.087738ms)
✔ CLI usage errors exit 2 (50.630519ms)
✔ credential.json sibling presence does not break validation and is never read (67.345203ms)
✔ D1 missing, empty and structurally empty roots refuse, no list projection (178.078068ms)
✔ D1 required directory auth cannot be absent (55.124406ms)
✔ D1 required directory auth/providers cannot be absent (55.174051ms)
✔ D1 required directory auth/accounts cannot be absent (65.607705ms)
✔ D1 required directory auth/settings cannot be absent (68.194942ms)
✔ D1 required directory harnesses cannot be absent (68.430456ms)
✔ D1 root file and unreadable metadata refuse (123.016733ms)
✔ D2 no symlink traversal at auth/providers/openai-codex.json (61.956083ms)
✔ D2 no symlink traversal at auth/accounts/openai-codex/homelab-openai (70.11802ms)
✔ D2 no symlink traversal at auth/providers (75.574364ms)
✔ D2 no symlink traversal at auth (59.487548ms)
✔ D2 root and ancestor symlinks and lexical traversal refuse (170.024215ms)
✔ private filesystem modes enforced for root (63.592633ms)
✔ private filesystem modes enforced for auth (53.667674ms)
✔ private filesystem modes enforced for auth/providers/openai-codex.json (56.37671ms)
✔ private filesystem modes enforced for auth/accounts/openai-codex/homelab-openai/account.json (58.259061ms)
✔ D3 numeric version 1 only across all record kinds (1.367263ms)
✔ D4 nested unknown keys and missing per-kind required fields refuse (63.141006ms)
✔ D5 unenrolled default refuses even when account exists (63.538674ms)
✔ D6 provider/account credential type must match (65.440288ms)
✔ D7 every harness endpoint enforces HTTP opt-in and shape (0.464132ms)
✔ D8 URLs reject embedded credentials and unsupported protocols without echo (174.497296ms)
✔ D9 malformed JSON diagnostics contain no content excerpt (75.363136ms)
✔ D10 missing metadata is missing-path, not invalid-json (71.448136ms)
✔ D10 library returns no partial entries on any invalid record (77.188608ms)
✔ null/scalar/array metadata refuses without stack or echo (238.266046ms)
✔ credential sibling is never opened, even when an unreadable symlink (36.426891ms)
✔ oversized metadata refuses before parsing (56.665304ms)
ℹ tests 69
ℹ suites 0
ℹ pass 69
@@ -74,4 +74,4 @@
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2245.097655
ℹ duration_ms 2258.430424
+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 (1315.813425ms)
✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (1081.115352ms)
✔ F1: an ordinary commit after update-ref is refused until step 8; then it commits and the queue stays at C (1324.965524ms)
✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (1069.545926ms)
ℹ 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 (1017.68701ms)
✔ F1: a `commit -e -- path` whose guard ran before update-ref fails at its own HEAD update (1091.836977ms)
ℹ 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 (1051.31222ms)
✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (1040.687515ms)
✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (1041.875297ms)
✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1357.055046ms)
✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (967.860816ms)
✔ F1: a shared-index change during the procedure is not committed (1109.614ms)
✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (1087.82765ms)
✔ F1: a missing or a different hook refuses (744.467811ms)
✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1257.445186ms)
✔ F1: the canary refuses a hook that git would not run (681.891727ms)
✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (980.199611ms)
✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (881.638875ms)
✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (911.897555ms)
✔ bootstrap: the archived tests and validator run outside any repository (943.738754ms)
✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (903.425594ms)
✔ general: a queue write after the snapshot is not committed (1305.011012ms)
✔ general: a snapshot whose log does not extend the base refuses (1021.887028ms)
✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (154.209801ms)
✔ general: environment overrides, a linked worktree and usage (770.148726ms)
✔ general: a queue path staged before the run refuses at step 1 (672.104169ms)
✔ general: HEAD's queue tests failing in the archive refuse (910.522991ms)
✔ genesis document serializes deterministically and replays (4.541225ms)
✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (2.647633ms)
✔ a tampered result, receipt or viewSha fails replay (2.377146ms)
✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.154114ms)
✔ add: defaults for an ordinary seat, privileged extras, refusals (3.687345ms)
✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (7.218915ms)
✔ matrix: release, review round, changes requested and waiting-on-jason (12.362106ms)
✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (9.377367ms)
✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (19.474585ms)
✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (2.520257ms)
✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1418.675427ms)
✔ matrix: blocked keeps the claim and returns only to previousState (3.604666ms)
✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.595486ms)
✔ field edits: who may change what (4.724985ms)
✔ set issues keeps a logged narrowing of closes (N10) (2.533927ms)
✔ text the table shows refuses \ and <, everywhere it enters (N8) (1.673054ms)
✔ every accepted text renders to nine cells on every row (N8) (22.975ms)
✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.519538ms)
✔ times and dates must be calendar values, not just the shape (2026-10-04) (1.719428ms)
✔ replay holds every op id to the caller's rule (N11) (5.497412ms)
✔ note: owner, listed reviewer or privileged; empty clears (1.299237ms)
✔ assign moves the claim with the owner; done clears it (2.632329ms)
✔ render is byte-stable and escapes pipes (0.526935ms)
✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.628848ms)
✔ next: resume, then review, then start, then wait, then nothing; lowest id first (21.047921ms)
✔ canonical args make a retry's identity independent of list order (0.313693ms)
✔ manifests, headings and blob ids (0.451512ms)
✔ the migration map: one queue-map block, exact keys (0.685253ms)
✔ every call but `queue` reaches the seat CLI exactly as before A2 (625.694927ms)
✔ `queue` reaches the queue CLI with the rest of the arguments (230.825945ms)
✔ the pre-A2 fixture is the script A2 changed (0.371132ms)
✔ acquire publishes the record by link; release removes only its own lock (10.852743ms)
✔ a kill between the temp write and the link leaves no lock (53.801935ms)
✔ a short or failed temp write refuses and leaves no lock and no temp (9.614997ms)
✔ a link error other than EEXIST refuses (10.524222ms)
✔ an error after the link releases the lock: unreadable gate, failing temp stat (22.084768ms)
✔ a release that fails on a gate path is reported, never a stack trace (P1) (38.708582ms)
✔ a paused holder: another writer waits 10 s, then refuses naming it live (10100.133413ms)
✔ two concurrent unlockers: the second refuses on the gate (39.149916ms)
✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (14.804068ms)
✔ a writer publishing during an unlock, gate first: the writer releases and refuses (19.392826ms)
✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (21.070219ms)
✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (18.357174ms)
✔ the same pid and start on a different boot is mismatch (0.782158ms)
✔ a foreign host is unknown whatever the local pid says; unlock refuses (68.810381ms)
✔ unreadable /proc: classification is unknown and acquire refuses (0.764468ms)
✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.193095ms)
✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (20.318818ms)
✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (28.304317ms)
✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (13.662347ms)
✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (29.026503ms)
✔ the migration map validates and renders the golden genesis table (3.535676ms)
✔ the marked QUEUE.md holds every row and parked item between its markers (1.323144ms)
✔ map-check reports each kind of drift (4.081592ms)
✔ a request posts once as the requester; a retry sends nothing (855.868743ms)
✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (4503.092654ms)
✔ the pre-send checks: GET user must name the requester, under the deadline (1589.810662ms)
✔ the lead's request refuses a token for login sage (920.746201ms)
✔ the credential file: the seat's own, 0600, no symlink, never the shared default (964.828249ms)
✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (2386.843801ms)
✔ a same-op retry after a kill sends nothing, even with a stale view (3202.595418ms)
✔ a held lock at the outcome exits 3 and names what the transport said (691.782854ms)
✔ late outcomes: after an abandon, and after a resolve with the same or another id (2338.703576ms)
✔ resolve checks the comment: issue, markers, round, candidate and author (1687.173607ms)
✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (725.637294ms)
✔ validateRow checks a request round's shape, which every replayed entry must keep (524.098243ms)
✔ request, changes, a new candidate, approval: every round pinned; no review files (1820.157622ms)
✔ a row with no reviewers opens a round that posts nothing (1223.172232ms)
✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1331.172512ms)
✔ semantics: v1 entries replay as before; review entries need v2 (490.341261ms)
✔ set reviewers refuses the row's owner (2026-09-28) (344.928618ms)
✔ the owner records no verdict, even as a listed reviewer (502.322231ms)
✔ a request comment over the length limit is not sent (593.527411ms)
✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (759.992641ms)
✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (2475.472287ms)
✔ genesis: refusals before anything is written (417.116561ms)
✔ genesis: the map must be committed, well formed, with committed briefs and seats (679.059586ms)
✔ genesis: markers, a stray witness, once only; a retry returns the receipt (558.113068ms)
✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (406.475892ms)
✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (682.226948ms)
✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (748.523026ms)
✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (743.149367ms)
✔ a retried add returns the id it first allocated, after reassignment and after done (903.80109ms)
✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (728.329374ms)
✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (1297.824741ms)
✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (850.318323ms)
✔ add, set reviewers and assign refuse the row's owner as a reviewer (600.731271ms)
✔ the working-brief check: a changed working copy refuses the start and flags next (713.071623ms)
✔ next: resume first, then nothing for an idle seat; needs a seat (320.325818ms)
✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (703.004529ms)
✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1199.175189ms)
✔ a hand edit to queue.json refuses every verb, reads included (601.872275ms)
✔ verify and render --check leave bytes and mtimes unchanged (432.353168ms)
✔ render is byte-stable across runs and repositories (300.566987ms)
✔ snapshot and verify --snapshot (817.800722ms)
✔ usage errors exit 4 (555.243725ms)
✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (284.484768ms)
✔ a rename failure: nothing visible, temp removed (169.193867ms)
✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (267.647309ms)
✔ a directory fsync failure, then sync names the op (386.599681ms)
✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (230.007017ms)
✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (202.930537ms)
✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (235.406465ms)
✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (183.252633ms)
✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (222.500035ms)
✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (195.858452ms)
✔ unlock prints a swapped gate's warning on stderr, the result on stdout (175.972928ms)
✔ a view write that fails keeps the op and reports a stale view (207.541223ms)
✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (689.655744ms)
✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (696.045038ms)
✔ SIGKILL after the witness, before the view: the stale refusal names the op (627.384ms)
✔ SIGKILL after the view, before the receipt: the retry returns the receipt (669.367102ms)
✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (254.179057ms)
✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (1053.58748ms)
✔ a deleted witness: refused after the locked recheck; accept-history records it absent (344.005ms)
✔ a header edit during a write: the op stands, the view write is skipped with a warning (202.885594ms)
✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (201.370104ms)
✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (236.401892ms)
✔ a writer paused before and after the witness rename: readers see a tail, then a match (239.793838ms)
✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (687.791547ms)
✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (645.274879ms)
✔ the platform check refuses other filesystems (158.550198ms)
✔ tmpfs passes only a test layer that allows it (N5) (226.220123ms)
✔ unlock keeps a multi-line lock record on stdout (P3) (205.315769ms)
✔ F1: step 8 with index.lock held exits 3, and ordinary commits stay refused until the printed command runs (1178.432914ms)
✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (1226.51663ms)
✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (1214.38844ms)
✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1436.845813ms)
✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (995.087339ms)
✔ F1: a shared-index change during the procedure is not committed (1152.559542ms)
✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (1066.061784ms)
✔ F1: a missing or a different hook refuses (752.89826ms)
✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1254.926539ms)
✔ F1: the canary refuses a hook that git would not run (711.558543ms)
✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (996.255958ms)
✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (940.929157ms)
✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (1127.235024ms)
✔ bootstrap: the archived tests and validator run outside any repository (933.119951ms)
✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (892.477365ms)
✔ general: a queue write after the snapshot is not committed (1503.673101ms)
✔ general: a snapshot whose log does not extend the base refuses (1079.409414ms)
✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (159.738017ms)
✔ general: environment overrides, a linked worktree and usage (629.320722ms)
✔ general: a queue path staged before the run refuses at step 1 (672.626209ms)
✔ general: HEAD's queue tests failing in the archive refuse (856.104785ms)
✔ genesis document serializes deterministically and replays (6.025791ms)
✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (3.307413ms)
✔ a tampered result, receipt or viewSha fails replay (3.307094ms)
✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.180195ms)
✔ add: defaults for an ordinary seat, privileged extras, refusals (4.430774ms)
✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (5.558526ms)
✔ matrix: release, review round, changes requested and waiting-on-jason (12.150619ms)
✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (8.739873ms)
✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (19.356517ms)
✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (2.343705ms)
✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1434.478155ms)
✔ matrix: blocked keeps the claim and returns only to previousState (3.724545ms)
✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.737006ms)
✔ field edits: who may change what (5.257737ms)
✔ set issues keeps a logged narrowing of closes (N10) (3.005939ms)
✔ text the table shows refuses \ and <, everywhere it enters (N8) (1.967734ms)
✔ every accepted text renders to nine cells on every row (N8) (27.287644ms)
✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.513896ms)
✔ times and dates must be calendar values, not just the shape (2026-10-04) (1.900355ms)
✔ replay holds every op id to the caller's rule (N11) (5.131008ms)
✔ note: owner, listed reviewer or privileged; empty clears (1.192835ms)
✔ assign moves the claim with the owner; done clears it (2.62756ms)
✔ render is byte-stable and escapes pipes (0.498811ms)
✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.542756ms)
✔ next: resume, then review, then start, then wait, then nothing; lowest id first (17.804243ms)
✔ canonical args make a retry's identity independent of list order (0.283935ms)
✔ manifests, headings and blob ids (0.411391ms)
✔ the migration map: one queue-map block, exact keys (0.52762ms)
✔ every call but `queue` reaches the seat CLI exactly as before A2 (674.544225ms)
✔ `queue` reaches the queue CLI with the rest of the arguments (195.352557ms)
✔ the pre-A2 fixture is the script A2 changed (0.290873ms)
✔ acquire publishes the record by link; release removes only its own lock (9.563753ms)
✔ a kill between the temp write and the link leaves no lock (51.394846ms)
✔ a short or failed temp write refuses and leaves no lock and no temp (6.803209ms)
✔ a link error other than EEXIST refuses (6.005551ms)
✔ an error after the link releases the lock: unreadable gate, failing temp stat (19.514826ms)
✔ a release that fails on a gate path is reported, never a stack trace (P1) (32.128036ms)
✔ a paused holder: another writer waits 10 s, then refuses naming it live (10096.79375ms)
✔ two concurrent unlockers: the second refuses on the gate (40.573245ms)
✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (24.490257ms)
✔ a writer publishing during an unlock, gate first: the writer releases and refuses (21.506575ms)
✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (20.288019ms)
✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (21.734301ms)
✔ the same pid and start on a different boot is mismatch (0.911222ms)
✔ a foreign host is unknown whatever the local pid says; unlock refuses (84.192907ms)
✔ unreadable /proc: classification is unknown and acquire refuses (0.761046ms)
✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.231473ms)
✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (21.32465ms)
✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (26.912867ms)
✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (5.802826ms)
✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (41.223634ms)
✔ the migration map validates and renders the golden genesis table (4.79073ms)
✔ the marked QUEUE.md holds every row and parked item between its markers (1.258254ms)
✔ map-check reports each kind of drift (4.415784ms)
✔ a request posts once as the requester; a retry sends nothing (905.088098ms)
✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (4705.943451ms)
✔ the pre-send checks: GET user must name the requester, under the deadline (1827.694497ms)
✔ the lead's request refuses a token for login sage (941.467669ms)
✔ the credential file: the seat's own, 0600, no symlink, never the shared default (866.334216ms)
✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (2369.942983ms)
✔ a same-op retry after a kill sends nothing, even with a stale view (3210.314414ms)
✔ a held lock at the outcome exits 3 and names what the transport said (695.500575ms)
✔ late outcomes: after an abandon, and after a resolve with the same or another id (2548.722514ms)
✔ resolve checks the comment: issue, markers, round, candidate and author (1716.454565ms)
✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (847.067684ms)
✔ validateRow checks a request round's shape, which every replayed entry must keep (544.335506ms)
✔ request, changes, a new candidate, approval: every round pinned; no review files (1799.442121ms)
✔ a row with no reviewers opens a round that posts nothing (1183.071596ms)
✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1371.550499ms)
✔ semantics: v1 entries replay as before; review entries need v2 (561.678604ms)
✔ set reviewers refuses the row's owner (2026-09-28) (416.725441ms)
✔ the owner records no verdict, even as a listed reviewer (669.107016ms)
✔ a request comment over the length limit is not sent (551.594405ms)
✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (664.607ms)
✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (2517.080568ms)
✔ genesis: refusals before anything is written (427.885336ms)
✔ genesis: the map must be committed, well formed, with committed briefs and seats (680.25551ms)
✔ genesis: markers, a stray witness, once only; a retry returns the receipt (554.26491ms)
✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (433.591615ms)
✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (668.163054ms)
✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (940.316189ms)
✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (779.602848ms)
✔ a retried add returns the id it first allocated, after reassignment and after done (992.408974ms)
✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (895.763614ms)
✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (1324.679043ms)
✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (838.423346ms)
✔ add, set reviewers and assign refuse the row's owner as a reviewer (520.931597ms)
✔ the working-brief check: a changed working copy refuses the start and flags next (694.243984ms)
✔ next: resume first, then nothing for an idle seat; needs a seat (323.350984ms)
✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (749.885267ms)
✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1166.173374ms)
✔ a hand edit to queue.json refuses every verb, reads included (579.327776ms)
✔ verify and render --check leave bytes and mtimes unchanged (470.413342ms)
✔ render is byte-stable across runs and repositories (345.591299ms)
✔ snapshot and verify --snapshot (844.369863ms)
✔ usage errors exit 4 (590.929916ms)
✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (294.991623ms)
✔ a rename failure: nothing visible, temp removed (214.12502ms)
✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (285.208379ms)
✔ a directory fsync failure, then sync names the op (395.573749ms)
✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (216.389577ms)
✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (199.305752ms)
✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (220.389696ms)
✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (213.091354ms)
✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (254.333892ms)
✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (255.382903ms)
✔ unlock prints a swapped gate's warning on stderr, the result on stdout (180.863444ms)
✔ a view write that fails keeps the op and reports a stale view (198.772798ms)
✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (773.517989ms)
✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (711.603704ms)
✔ SIGKILL after the witness, before the view: the stale refusal names the op (643.250736ms)
✔ SIGKILL after the view, before the receipt: the retry returns the receipt (710.760378ms)
✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (322.711236ms)
✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (1134.208445ms)
✔ a deleted witness: refused after the locked recheck; accept-history records it absent (408.04457ms)
✔ a header edit during a write: the op stands, the view write is skipped with a warning (230.851588ms)
✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (220.343628ms)
✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (224.916508ms)
✔ a writer paused before and after the witness rename: readers see a tail, then a match (228.249859ms)
✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (601.437386ms)
✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (603.09745ms)
✔ the platform check refuses other filesystems (172.544266ms)
✔ tmpfs passes only a test layer that allows it (N5) (225.79821ms)
✔ unlock keeps a multi-line lock record on stdout (P3) (232.916337ms)
ℹ tests 148
ℹ suites 0
ℹ pass 148
@@ -155,4 +155,4 @@
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 30007.76345
ℹ duration_ms 30990.382954
+28 -28
View File
@@ -1,30 +1,30 @@
✔ resolveSeat: by name under --repo resolves the repo layout (1.245323ms)
✔ resolveSeat: by path resolves the fleet layout (0.310504ms)
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.697631ms)
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.632989ms)
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.786135ms)
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.257165ms)
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.105947ms)
✔ CLI launch: registers, execs the fake launch script, and passes args through (31.799286ms)
✔ CLI launch: --harness lands in the record (29.529466ms)
✔ CLI launch: the launch script's own exit code passes through (29.242989ms)
✔ CLI launch: relaunching a seat rewrites the one registration record (55.726663ms)
✔ CLI launch: omitting --task records an empty string, not null (30.591302ms)
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (80.218911ms)
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (134.378932ms)
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.403602ms)
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.532978ms)
✔ 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.718033ms)
✔ 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.848881ms)
✔ 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 (268.708629ms)
✔ family: exactly one launch.max key in the model name, else null (0.713929ms)
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.664675ms)
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.485059ms)
✔ session file and launch log: 0600, the session file written once (0.953821ms)
✔ endReason maps the runner's exit codes; a signal is killed (0.111125ms)
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.285128ms)
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (1.017973ms)
✔ a session runs under unshare as pid 1 of its namespace, claims, answers, and stops on mosaic stop (246.632034ms)
✔ resolveSeat: by name under --repo resolves the repo layout (1.319343ms)
✔ resolveSeat: by path resolves the fleet layout (0.336467ms)
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.765941ms)
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.678008ms)
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.83333ms)
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.31461ms)
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.206706ms)
✔ CLI launch: registers, execs the fake launch script, and passes args through (31.07694ms)
✔ CLI launch: --harness lands in the record (34.419142ms)
✔ CLI launch: the launch script's own exit code passes through (31.343789ms)
✔ CLI launch: relaunching a seat rewrites the one registration record (61.170784ms)
✔ CLI launch: omitting --task records an empty string, not null (34.172856ms)
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (83.178003ms)
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (140.712262ms)
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.469104ms)
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (1.461526ms)
✔ 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.754913ms)
✔ 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.88094ms)
✔ 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 (305.051589ms)
✔ family: exactly one launch.max key in the model name, else null (0.882429ms)
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.899512ms)
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.695646ms)
✔ session file and launch log: 0600, the session file written once (1.399717ms)
✔ endReason maps the runner's exit codes; a signal is killed (0.136655ms)
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.737279ms)
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (1.649146ms)
✔ a session runs under unshare as pid 1 of its namespace, claims, answers, and stops on mosaic stop (252.111361ms)
ℹ tests 27
ℹ suites 0
ℹ pass 27
@@ -32,4 +32,4 @@
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 719.297907
ℹ duration_ms 786.592101
+52 -52
View File
@@ -1,54 +1,54 @@
✔ the boot config is checked before anything starts (119.97767ms)
✔ a business with no tracker entry refuses task verbs (137.857625ms)
✔ credential.expiring and .expired are recorded once per instance (223.935762ms)
✔ a token file that changes on disk records credential.changed (120.172495ms)
✔ autostart polls, reconciles and retries a startup the tracker was down for (145.800704ms)
✔ a refusal a restart must clear is not retried by the poll (113.632769ms)
✔ a poll that fires while two are queued is dropped (105.358072ms)
✔ close waits for a running verb and refuses one that has not started (249.063575ms)
✔ the bundled Vikunja is the pinned upstream image the runbook names (0.71601ms)
✔ every published port is on 127.0.0.1, and no secret is in the file (0.256866ms)
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (418.468613ms)
✔ the recorded task bodies pass the checks S3 applies to every read (0.562714ms)
✔ the client works against the fake over real HTTP with the platform fetch (269.350239ms)
✔ a correct install starts, and the first reconcile records tasks that already exist (130.356365ms)
✔ verbs refuse while a business is starting and after startup refused it (145.385943ms)
✔ startup refuses a token that can do more than its role needs (406.541994ms)
✔ startup refuses an unsupported version and flags an untested one (319.754907ms)
✔ startup refuses a board that the runbook did not install (370.242996ms)
✔ startup refuses a project the sync bot cannot read (83.884438ms)
✔ startup refuses a configured label the pm bot cannot see (103.105883ms)
✔ startup refuses an expired credential and a missing sync credential (183.15347ms)
✔ an unreachable tracker refuses with tracker-unavailable (87.4792ms)
✔ an edit in the UI is recorded once, with the fields that changed (234.03919ms)
✔ a move between open buckets is seen on the board, though updated does not change (184.384822ms)
✔ a person's comment is counted and a bot's is not (288.788654ms)
✔ the hourly reconcile catches a comment through comment_count (237.351272ms)
✔ a task closed in the UI leaves the open view with its done bucket (442.960893ms)
✔ a task that leaves the board is recorded as deleted, moved or out of reach (271.049202ms)
✔ a poll that read before a verb wrote does not overwrite the verb (178.64957ms)
✔ a tracker fault during a tick is reported and the next tick catches up (173.029944ms)
✔ a malformed answer refuses the tick with tracker-shape (142.07532ms)
✔ no token value reaches the database, the log or a refusal (284.205053ms)
✔ the first look at a task counts only comments inside the window (182.927221ms)
✔ task.create needs a recorded human request and a requirement id (234.003886ms)
✔ only labels named in the business file can be written (229.763681ms)
✔ task.schedule sets and clears a due date and relations (319.86945ms)
✔ assign and reassign move the role bots and record task.assigned (336.838827ms)
✔ task.update.assigned is for the assignee and records task.state (338.823149ms)
✔ a wrong expected digest records task.conflict and writes nothing (215.122804ms)
✔ a cross-role verb needs a resolved decision, used once (278.383423ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (255.889778ms)
✔ a lost answer is settled by a re-read and never retried (273.373847ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (182.244162ms)
✔ a task the sync bot cannot read refuses and records nothing (104.189372ms)
✔ verbs and polls for one business run one at a time (172.998567ms)
✔ a due date with milliseconds is written to the second (171.413102ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (117.387915ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (84.895896ms)
✔ a create whose final read fails succeeds and records task.created (93.866128ms)
✔ an edit between the last write and the final read shows as external on the next poll (117.570787ms)
✔ task.created is recorded when a later label write fails (84.597114ms)
✔ the boot config is checked before anything starts (86.53616ms)
✔ a business with no tracker entry refuses task verbs (121.033728ms)
✔ credential.expiring and .expired are recorded once per instance (238.662195ms)
✔ a token file that changes on disk records credential.changed (110.167027ms)
✔ autostart polls, reconciles and retries a startup the tracker was down for (137.516851ms)
✔ a refusal a restart must clear is not retried by the poll (83.605374ms)
✔ a poll that fires while two are queued is dropped (102.558062ms)
✔ close waits for a running verb and refuses one that has not started (218.733326ms)
✔ the bundled Vikunja is the pinned upstream image the runbook names (0.943209ms)
✔ every published port is on 127.0.0.1, and no secret is in the file (0.356824ms)
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (375.792226ms)
✔ the recorded task bodies pass the checks S3 applies to every read (0.594935ms)
✔ the client works against the fake over real HTTP with the platform fetch (247.020492ms)
✔ a correct install starts, and the first reconcile records tasks that already exist (107.080549ms)
✔ verbs refuse while a business is starting and after startup refused it (156.327278ms)
✔ startup refuses a token that can do more than its role needs (390.24299ms)
✔ startup refuses an unsupported version and flags an untested one (257.301253ms)
✔ startup refuses a board that the runbook did not install (377.532789ms)
✔ startup refuses a project the sync bot cannot read (87.091341ms)
✔ startup refuses a configured label the pm bot cannot see (98.739148ms)
✔ startup refuses an expired credential and a missing sync credential (176.990677ms)
✔ an unreachable tracker refuses with tracker-unavailable (72.813225ms)
✔ an edit in the UI is recorded once, with the fields that changed (198.316094ms)
✔ a move between open buckets is seen on the board, though updated does not change (195.702643ms)
✔ a person's comment is counted and a bot's is not (282.562191ms)
✔ the hourly reconcile catches a comment through comment_count (230.197458ms)
✔ a task closed in the UI leaves the open view with its done bucket (392.842554ms)
✔ a task that leaves the board is recorded as deleted, moved or out of reach (272.858491ms)
✔ a poll that read before a verb wrote does not overwrite the verb (166.093849ms)
✔ a tracker fault during a tick is reported and the next tick catches up (163.758114ms)
✔ a malformed answer refuses the tick with tracker-shape (127.398439ms)
✔ no token value reaches the database, the log or a refusal (270.094556ms)
✔ the first look at a task counts only comments inside the window (137.891107ms)
✔ task.create needs a recorded human request and a requirement id (188.444054ms)
✔ only labels named in the business file can be written (244.533409ms)
✔ task.schedule sets and clears a due date and relations (289.26533ms)
✔ assign and reassign move the role bots and record task.assigned (294.57936ms)
✔ task.update.assigned is for the assignee and records task.state (299.706629ms)
✔ a wrong expected digest records task.conflict and writes nothing (196.565364ms)
✔ a cross-role verb needs a resolved decision, used once (254.681696ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (217.486778ms)
✔ a lost answer is settled by a re-read and never retried (252.812073ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (174.684319ms)
✔ a task the sync bot cannot read refuses and records nothing (105.904241ms)
✔ verbs and polls for one business run one at a time (171.006531ms)
✔ a due date with milliseconds is written to the second (168.703594ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (107.939507ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (88.213901ms)
✔ a create whose final read fails succeeds and records task.created (94.169486ms)
✔ an edit between the last write and the final read shows as external on the next poll (115.615544ms)
✔ task.created is recorded when a later label write fails (79.373231ms)
ℹ tests 51
ℹ suites 0
ℹ pass 51
@@ -56,4 +56,4 @@
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3697.915463
ℹ duration_ms 3446.444304
+15 -15
View File
@@ -1,18 +1,18 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3036.471142ms)
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3469.489458ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3034.366279ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3015.132576ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1653.090341ms)
✔ conversation view: a newer session with no readable history keeps the marker (1205.30957ms)
✔ conversation view: seats without history say so and offer no reply (746.156155ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2896.507237ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (52750.187787ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3001.025458ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21708.6077ms)
✔ loopback host and board origin fail closed (6.090954ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (77.465842ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (55.459641ms)
✔ unreachable board reports URL; CLI rejects unsupported options (965.757487ms)
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3364.830245ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3544.973289ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1654.619907ms)
✔ conversation view: a newer session with no readable history keeps the marker (1085.988414ms)
✔ conversation view: seats without history say so and offer no reply (793.672153ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3334.88261ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54055.861502ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3478.193516ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21953.046074ms)
✔ loopback host and board origin fail closed (6.453466ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (90.944633ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (125.009085ms)
✔ unreachable board reports URL; CLI rejects unsupported options (954.243888ms)
ℹ tests 14
ℹ suites 0
ℹ pass 14
@@ -20,4 +20,4 @@ Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53029.312669
ℹ duration_ms 54438.577036
+2 -2
View File
@@ -1,10 +1,10 @@
node-bus exit=0 ℹ pass 74 ℹ fail 0
node-business exit=0 ℹ pass 60 ℹ fail 0
node-cli exit=0 ℹ pass 77 ℹ fail 0
node-cli exit=0 ℹ pass 82 ℹ fail 0
node-control-board exit=0 ℹ pass 124 ℹ fail 0
node-conversation exit=0 ℹ pass 152 ℹ fail 0
node-discord exit=0 ℹ pass 178 ℹ fail 0
node-harness exit=0 ℹ pass 45 ℹ fail 0
node-harness exit=0 ℹ pass 50 ℹ fail 0
node-ledger exit=0 ℹ pass 78 ℹ fail 0
node-mosaic exit=0 ℹ pass 69 ℹ fail 0
node-queue exit=0 ℹ pass 148 ℹ fail 0
+34 -2
View File
@@ -1,7 +1,39 @@
On branch refactor
Your branch is up to date with 'origin/refactor'.
Note: switching to '8dd5ff004b0ce6157446ef78523a1036ed86f42c'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
Not currently on any branch.
nothing to commit, working tree clean
Note: switching to '8dd5ff004b0ce6157446ef78523a1036ed86f42c'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
OK dry-run committed nothing
OK apply: allowed change exits 0 (exit 0)
@@ -8,7 +8,7 @@ OK check detects an extra destination directory
OK check rejects a destination symlink
OK sync accepts a canonical source update
OK updated installation matches canonical source
scripts/test-extension-package.sh: line 14: 4103097 Killed "$@" > /dev/null 2>&1
scripts/test-extension-package.sh: line 14: 945540 Killed "$@" > /dev/null 2>&1
OK forced interruption kills the replacing process
OK next invocation recovers old consistent installation
OK interrupted replacement rolled back
+1 -1
View File
@@ -30,6 +30,6 @@ OK packages/queue declares no dependencies
ℹ fail 0
OK node --test packages/queue/tests/
OK scripts/mosaic queue help
skip queue verify and render --check: this checkout (/home/jwoltje/filbert-scratch/s6-gate2) 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-r2) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
queue suite: 27 passed, 0 failed
+21 -21
View File
@@ -1,29 +1,29 @@
5dc9e67c9ce42b86c40d83c8b1aa8d5ba93d6e8b51bb1f4d2964f4b270ee52aa base.txt
ff57f58ab3fea8bd08d44046c66afa3c9dbdbe315af1884fe5f66e1914b870cf BUILD.md
dc346027db9b650c70b57ff390b57a8d76d3e2ccd434538cb083fe9a97ea4454 build.patch
5b3a934d01eb518e23b842623aeb3cbfc287f1b88110edc1e044d4131b2927f3 candidate-manifest.sha256
ec2844698bd087200858279bd145060855374f88bad98a94e2a31d94a34cff61 files.txt
610db30b90e3ce17614bf8d806f35102947deb8396f0045eec0c58b22aca549d BUILD.md
f8cbf7bde355312253265d2071e5756f4b04d116bac06cfcf0a4a2c0474da942 build.patch
fd21bdc27573f080d965da3121a390b148a3370acd8d9dfa5d34fcd59460eb02 candidate-manifest.sha256
c6309079b471a4b304ba55679bfa2ce7f1e145137ec784f2e35ee7330df844ea files.txt
8df77c49bf82833efa56d42401a4a06f20931b7a76dd5eed3a73fdec60e92e79 out/base-test-task.txt
c6d438dd3ffa913d2e05aeb6fb65052285d06f29fc9acd4c10f58c23515614a6 out/node-business.txt
9787d63649bbe350b9f5ce5f4f14fc95e5a2def3269db0124dd992a55c91d789 out/node-bus.txt
f8b612d08369d22f4bedc7d026a63a7a9592f652ab7602bab27e98daea36eb90 out/node-cli.txt
3d85276fa1675f46c8b26e2600cb8650d50231f8b39b5f142dae6615b73e6ce2 out/node-control-board.txt
927a609029f89118e2ce57fe77e2435669c7a6b49d1b69c783f9ff0dcd0fa636 out/node-conversation.txt
0d136f1b18657e38974378e579efc79080dc90c7ab94adb56a00d9861b45917c out/node-discord.txt
bab5785db84cdd60bccbe263b0c5c692471b8eed539729a9a9596697f2cd110f out/node-harness.txt
a43ef2dbaba0f0f8f17ae84296e08e6067b97c0467cbe89337a73f3bce01b8d5 out/node-ledger.txt
04ed00245b6c46f554f6d4453ac9455f79e9571f5eaf05021c03f6d03f1276cb out/node-mosaic.txt
39bb0c4e1030d5e94f1efdb0de1bc77c5c4baef65b1b39c1fceaa70d6bf9a4e6 out/node-queue.txt
97d94f5e9d1a715fab380ca7f1afa5ce1806a700923186bf290036b83656ba1c out/node-seat.txt
cd90ab3dc11bc8c457fc7d33ab9aac570d1bc2854025a089e3630bea01bb008f out/node-tasks.txt
c0310ed920b5b5b1f1eaa8a51cd5ae2721c0eb404889d024127939faa5c1c497 out/node-webui.txt
5a1bb131fdc9f3bb09829fabd9cd3f58cd649db694cd4d69d3e391b5aa1faf9a out/summary.txt
d72ae9083340920f68e4f549d110810d8790450cb09920745e969af2b2568145 out/node-business.txt
7eaf5878f8e31e7eb01c51c8daf52a8be2e983b3fd9c4d3b98fb2243476ccbb4 out/node-bus.txt
a83e51052bdb03b412b1451002bafad57ad89ceffc73baf4be3caa61fb8f4200 out/node-cli.txt
fe7f83107c5a25fe617f8e58413a9ff704e140c1884dc6f241c58d55bd9912d7 out/node-control-board.txt
b60466aba670a9b30a8537e24b9f291d5cbe49befe13c31c6781e3c46b8889e0 out/node-conversation.txt
e58a0e4907739b6c79ca291b428bbd5160f2444fca78656bde0283f92032891f out/node-discord.txt
1c6c21518e709a43acbf3ca4a4a7eb89cda52cf9ecc3a2a55d3c9160c0129ba8 out/node-harness.txt
7e47533785ff363fe2f7e9a3ff6fdd308f6f161578c15b836fce96a0730e4466 out/node-ledger.txt
493b230afa7be6bdbc8baad8430fb32f8e0450340c609d91648d2b2c2344db14 out/node-mosaic.txt
634086ba61d96f9bd421fdda0d1c20e08760980fbce6f70056a31586f2e0fb21 out/node-queue.txt
75f62e174199cae604672b2cc883dc0d8588816db63048090d03290f2b67bc93 out/node-seat.txt
70d50a65bf6e55adf11a9ed594f3ad3f37a75ba54ca9febf4746df82f42c6a35 out/node-tasks.txt
43f03acf96397b1658e7b0eae7136feae19f052ae323a69aef28a77c6c035a46 out/node-webui.txt
5790ec180458b55445a450625a583e09b6a5de5d820f062be4ff500074c8a9e4 out/summary.txt
f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 out/test-auth.txt
e1206366f07f3d9029530dcdcb55c0282d3f052adc60fc7fd51642d5d4ec3600 out/test-conductor.txt
b13547b025c824fae58dcb2d15bdac9d331a7965218e1c7491814020572984da out/test-conductor.txt
52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 out/test-config.txt
7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe out/test-discord.txt
89e5e21216ce3dfbdd4d9ebaaeb2cb1a3f8a03e34e458ffa948c6d478f375dff out/test-extension-package.txt
836c9cbc92e8c384b80a17e8e31a7ece39aca33efcad61b86898293e8d2f2cc3 out/test-extension-package.txt
83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f out/test-foundation.txt
75549ab0587c1bb5e06f9acd0e49ca21627fd936e5e2d94121ed8fe1d6e8898b out/test-queue.txt
45f31e21b7685482530fc5bae64e910462e4c6f2c1554e56465d098d0faf3da0 out/test-queue.txt
6183e6b9b05edb497e92ffd09a19bba49c6f656c750ecb2acc283021113d1a98 out/test-release.txt
8df77c49bf82833efa56d42401a4a06f20931b7a76dd5eed3a73fdec60e92e79 out/test-task.txt