docs: Researcher row 109 round 3 packet (#1603): seat session, cli launcher and seat sandbox tests re-anchored, N1 comment, two N2 tests, 11-file candidate manifest
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
Researcher, 2026-10-13. Brief: `docs/plans/2026-10-13_pm-tester-and-fresh-state.md`, "A new PM run reads current launch state and task status before it plans". BASE: `02fe2d9d5` (origin/refactor after row 106 landed as `8b58cb7e4`). Reviewer: Filbert. Gate owner: Sage.
|
||||
|
||||
Candidate change: `row109.patch` (8 files: 7 modified, 1 new). Built and run in `/mnt/storage/scratch/researcher/q109`, detached at `02fe2d9d5`, `node_modules` linked to the canonical one. The patch applies to BASE; the packet is not in it.
|
||||
Candidate change: `row109.patch` (11 files: 10 modified, 1 new; round 3, 299 insertions and 11 deletions. Round 1 and 2 had 8 files; sections below describe round 1 unless marked round 3). Built and run in `/mnt/storage/scratch/researcher/q109`, detached at `02fe2d9d5`, `node_modules` linked to the canonical one. The patch applies to BASE; the packet is not in it.
|
||||
|
||||
## What ships
|
||||
|
||||
@@ -22,7 +22,7 @@ Candidate change: `row109.patch` (8 files: 7 modified, 1 new). Built and run in
|
||||
|
||||
## Tests
|
||||
|
||||
- `packages/bus/tests/state.test.mjs` (new, 6): never switched reads `allowed` with null time and event; revoked then restored reads `allowed` with the restore event's time and id (and `revoked` with the revoke id in between); a task closed on the tracker leaves the open list and `total` drops; a tombstone is not open; the list stops at the cap and says so while `total` counts all; role and reader sessions read the same state, an argument refuses `invalid-request`, an unbound cap refuses `unauthenticated`.
|
||||
- `packages/bus/tests/state.test.mjs` (new; 6 in round 1, 8 in round 3): never switched reads `allowed` with null time and event; revoked then restored reads `allowed` with the restore event's time and id (and `revoked` with the revoke id in between); a task closed on the tracker leaves the open list and `total` drops; a tombstone is not open; the list stops at the cap and says so while `total` counts all; role and reader sessions read the same state, an argument refuses `invalid-request`, an unbound cap refuses `unauthenticated`.
|
||||
- `packages/harness/tests/runner.test.mjs` (6 new tests, 20 in the file; harness suite 73 -> 79). By name:
|
||||
1. `turnRequest shows the message's own sent time, a day old as well as new`
|
||||
2. `stateBlock names the launch state with its time and event, and the open tasks with the cap`
|
||||
@@ -56,7 +56,7 @@ BASE `02fe2d9d5`; slot held 23:04:51Z to 23:08:36Z, "clear" sent; no process of
|
||||
|
||||
| suite | result | Sage's gate count |
|
||||
|---|---|---|
|
||||
| `node --test packages/bus/tests/*.test.mjs` | 134 pass, 0 fail | 128 (+6 new) |
|
||||
| `node --test packages/bus/tests/*.test.mjs` | 134 pass, 0 fail (round 3: 136) | 128 (+6 new) |
|
||||
| `node --test packages/harness/tests/*.test.mjs` | 79 pass, 0 fail | 73 (+6 new) |
|
||||
| `node --test packages/queue/tests/*.test.mjs` | 156 pass, 0 fail | 156 |
|
||||
| `scripts/test-release.sh` | 14 passed, 0 failed | 14 |
|
||||
@@ -71,4 +71,52 @@ The `queue verify and render --check` case of `test-queue.sh` is skipped in a wo
|
||||
|
||||
## Scope and live
|
||||
|
||||
Owned files only, plus the accepted fixture. Live use needs a new copy and re-pin under Mos's go, with decision 109 notices before and after (runner.mjs reaches new role sessions at their next start; the broker verb needs a bus restart).
|
||||
Owned files only, plus the accepted fixture and, in round 3, three test files (see below). Live use needs a new copy and re-pin under Mos's go, with decision 109 notices before and after (runner.mjs reaches new role sessions at their next start; the broker verb needs a bus restart).
|
||||
|
||||
## Round 3 (Filbert's round 2 request changes, comment 28329, rev 1074)
|
||||
|
||||
BASE is still `02fe2d9d5`. Candidate: `row109.patch` regenerated with `git diff 02fe2d9d5` (11 files). The candidate manifest lists those 11 files, post-patch; the packet digests are in `packet-manifest.sha256`.
|
||||
|
||||
- **B1 (two tests outside the manifest failed).** Every request now opens with the state block, so two tests that matched `/^echo: Message .* from <role>, class REQUEST:\n\nhello/` failed on the patch. Both are re-anchored on the new shape (state block, then the header with the sent time, then `hello`); neither regex was loosened to skip the block. Test edits only; no seat or cli source changed.
|
||||
- `packages/seat/tests/session.test.mjs`: the sent time is read from the store (`messages.at`) and matched exactly. 9/9.
|
||||
- `packages/cli/tests/launcher.test.mjs`: there is no store handle, so the sent time is captured from the echo and must fall between the moment before the send and the moment of the check; the sender (`cto`) and the message id are matched exactly. 17/17.
|
||||
- **A third consumer, found by the full package run.** `packages/seat/tests/sandbox.test.mjs` failed 2 of 7 on the patch (clean base: 7/7). Its `adapter.sh` took the body with `sed '1,/^$/d'`, i.e. everything after the first blank line; the state block puts a blank line above the header, so the command handed to `/bin/sh` was wrong and every sandbox check failed. The line is now `sed '1,/^Message .*:$/d' | sed '1d'`: skip through the `Message ...:` header and the blank line after it. Test edit only. Outside the two files named in Sage's ruling; flagged to Sage in the "clear" message. Check that no other consumer parses the request, from the repo root of the patched tree:
|
||||
|
||||
```
|
||||
grep -rIn MOSAIC_REQUEST packages scripts adapters src | grep -v node_modules | grep -v '^packages/harness/'
|
||||
```
|
||||
|
||||
It lists only: `packages/seat/tests/sandbox.test.mjs` (the line fixed here); `adapters/mock/adapter.sh`, `adapters/pi/adapter.sh`, `adapters/claude/adapter.sh` (each requires the variable to be set, and pi and claude pass the whole text as the prompt: `-p "$MOSAIC_REQUEST"`; none parses the body); `adapters/README.md` (the contract table); `src/run-agent.sh` (sets the variable for the older run path). Inside `packages/harness/` the uses are the runner (sets it), `tests/fixtures/fake-adapter.mjs` (fixed in round 1) and the two session tests, which pass a literal request to the real adapter scripts.
|
||||
- **N1.** The broker comment on `state` now says "role, reader and human sessions", matching the code (human sessions skip the agent check).
|
||||
- **N2.** Two new tests in `packages/bus/tests/state.test.mjs` (now 8): `a human session reads the same state, with the same cap` (equal to a role session's read, cap and total unchanged at 50 and 52); `reading state writes nothing: every table keeps its row count` (every table counted before and after a read by a role, a reader and a human session).
|
||||
|
||||
### Round 3 mutants
|
||||
|
||||
`mutants.sh` gained controls for seat and cli and six mutants. `mutants/` was rebuilt in this round: all 12 killed, all 4 controls pass.
|
||||
|
||||
| mutant | edit | test file | result | failing tests |
|
||||
|---|---|---|---|---|
|
||||
| control-runner / bus / seat / cli | none | runner, state, session, launcher | 20/20, 8/8, 9/9, 17/17 | none |
|
||||
| m1, m2, m3 | as above | runner | 17/20, 17/20, 19/20 | as above |
|
||||
| m4, m5, m6 | as above | state | 7/8, 7/8, 5/8 | as above (plus the human-session test under m6) |
|
||||
| m7 | request without the block | seat session | 8/9 | "a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop" |
|
||||
| m8 | header without `sent <at>` | seat session | 8/9 | the same test |
|
||||
| m9 | request without the block | cli launcher | 16/17 | "the PM launches a coder through its launch tool; the coder answers; refusals name their code" |
|
||||
| m10 | header without `sent <at>` | cli launcher | 16/17 | the same test |
|
||||
| m11 | `state` checks a human session as an agent | state | 6/8 | "a human session reads the same state, with the same cap" and one earlier test |
|
||||
| m12 | `state` writes an `action.allowed` event | state | 7/8 | "reading state writes nothing: every table keeps its row count" |
|
||||
|
||||
After the run the candidate carried none of the mutations (greps on each edited expression) and no `.bak` directory was left.
|
||||
|
||||
### Round 3 suites (q109, outputs in `suites/`)
|
||||
|
||||
`suites.sh` now runs all 14 package node suites. Slot held from 23:39:54Z, "clear" sent after the mutants.
|
||||
|
||||
| suite | result |
|
||||
|---|---|
|
||||
| bus | 136/136 (134 + the 2 N2 tests) |
|
||||
| business 67, cli 168, control-board 124, conversation 182, discord 178, harness 79, ledger 78, mosaic 69, queue 156, runs 43, tasks 92, webui 108 | 0 fail each |
|
||||
| seat | 119/119 on the rerun. The first run was 117/119; the 2 failures were `sandbox.test.mjs`, fixed as above. The first run's output was overwritten by the rerun; the failure and its cause are in this file and in the "clear" message. |
|
||||
| `scripts/test-*.sh` | auth 15, conductor 17, config 24, discord 66, extension-package 18, foundation 44, queue 28, release 14, task 98; 0 failed each |
|
||||
|
||||
The `queue verify and render --check` case of `test-queue.sh` is skipped in this tree as before; Sage's gate rerun covers it. After the run I removed the `q109_default` Docker network and the clean-base worktree I used for the control run; no node or chromium process of mine remains.
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
f097eea6ea20f1972d543c560cfe70d7c355be2d446903a466e3a488c43c8a0f packages/bus/README.md
|
||||
d4e936b635648955585af332bfc67df01991b6f4b233d03ea52266251cdee120 packages/bus/src/broker.mjs
|
||||
2e5150e630d3f360ccea773ac5a6e8434cb4b957efb49c556252ab222e7ae46f packages/bus/tests/state.test.mjs
|
||||
08f662eb73dd77f5bf5d7548d90c5205ae683b3f8e2e407fec682f0d4bb157a8 packages/bus/src/broker.mjs
|
||||
0bbcd49d8716d6a72cd405d67c9b4d88ec6594d5d4b434c24aab9fb97996123d packages/bus/tests/state.test.mjs
|
||||
54f800e2b98f00737e5fce144fa3ef3014e5d7d53348217a16b6438bf620f5e3 packages/cli/tests/launcher.test.mjs
|
||||
71eb94d57b18a64ec7cb4fe69d1b9778ccebbdf5db0064a3f1829dc018844892 packages/harness/README.md
|
||||
5140e415c2e8954923837ef87cceaa21536009517e46db1cd72a5c6189667552 packages/harness/src/runner.mjs
|
||||
3c1c7c62fe87026c091c05433682ce23b9cb712122ea2c124806efd191481e04 packages/harness/tests/fixtures/fake-adapter.mjs
|
||||
02c9f50d381ee699665d4485066012ec7eb14bf1b9feb1f803caec4ff78531d4 packages/harness/tests/runner.test.mjs
|
||||
2a02a325bac8d6d4562d028f0ae03ef12b3ba17f6df129a83a1824bd41a82e49 packages/seat/tests/sandbox.test.mjs
|
||||
bf0bd4fb4864cc5cb023404ac090b082ed7d7f41d21e46c78fff7e78d7adf6fd packages/seat/tests/session.test.mjs
|
||||
4900cf07d7089775cce191f8ded25f1f832e61b98dae8010c2480df9f093f0de roles/pm.md
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
seat node suite: rc=0 ℹ tests 119 ℹ pass 119 ℹ fail 0
|
||||
control-runner: exit 0; ℹ tests 20 ℹ pass 20 ℹ fail 0
|
||||
control-bus: exit 0; ℹ tests 6 ℹ pass 6 ℹ fail 0
|
||||
control-bus: exit 0; ℹ tests 8 ℹ pass 8 ℹ fail 0
|
||||
m1-drop-block: exit 1; ℹ tests 20 ℹ pass 17 ℹ fail 3
|
||||
failed: ✖ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0
|
||||
failed: ✖ a task closed on the tracker is missing from the next turn's open list
|
||||
@@ -13,13 +14,36 @@ m2-drop-time: exit 1; ℹ tests 20 ℹ pass 17 ℹ fail 3
|
||||
m3-drop-failure-check: exit 1; ℹ tests 20 ℹ pass 19 ℹ fail 1
|
||||
failed: ✖ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read
|
||||
failed: ✖ failing tests:
|
||||
m4-oldest-launch-event: exit 1; ℹ tests 6 ℹ pass 5 ℹ fail 1
|
||||
m4-oldest-launch-event: exit 1; ℹ tests 8 ℹ pass 7 ℹ fail 1
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ launches revoked and then restored read allowed, with the restore time and event id
|
||||
m5-closed-in-open: exit 1; ℹ tests 6 ℹ pass 5 ℹ fail 1
|
||||
m5-closed-in-open: exit 1; ℹ tests 8 ℹ pass 7 ℹ fail 1
|
||||
failed: ✖ a task closed on the tracker leaves the open list, and total counts the open ones
|
||||
failed: ✖ failing tests:
|
||||
m6-cap-unstated: exit 1; ℹ tests 6 ℹ pass 4 ℹ fail 2
|
||||
m6-cap-unstated: exit 1; ℹ tests 8 ℹ pass 5 ℹ fail 3
|
||||
failed: ✖ a human session reads the same state, with the same cap
|
||||
failed: ✖ a task closed on the tracker leaves the open list, and total counts the open ones
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ the open list stops at the cap and says so; total still counts every open task
|
||||
control-seat: exit 0; ℹ tests 9 ℹ pass 9 ℹ fail 0
|
||||
control-cli: exit 0; ℹ tests 17 ℹ pass 17 ℹ fail 0
|
||||
m7-seat-drop-block: exit 1; ℹ tests 9 ℹ pass 8 ℹ fail 1
|
||||
failed: ✖ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop
|
||||
failed: ✖ failing tests:
|
||||
m9-cli-drop-block: exit 1; ℹ tests 17 ℹ pass 16 ℹ fail 1
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code
|
||||
m8-seat-drop-time: exit 1; ℹ tests 9 ℹ pass 8 ℹ fail 1
|
||||
failed: ✖ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop
|
||||
failed: ✖ failing tests:
|
||||
m10-cli-drop-time: exit 1; ℹ tests 17 ℹ pass 16 ℹ fail 1
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code
|
||||
m11-human-checked-as-agent: exit 1; ℹ tests 8 ℹ pass 6 ℹ fail 2
|
||||
failed: ✖ a human session reads the same state, with the same cap
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ reading state writes nothing: every table keeps its row count
|
||||
m12-state-writes-event: exit 1; ℹ tests 8 ℹ pass 7 ℹ fail 1
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ reading state writes nothing: every table keeps its row count
|
||||
mutants-done
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 109 (#1603): each new check must fail on a mutant. For each mutant this runs the named test
|
||||
# Row 109 (#1603), round 3 adds m7-m12: the two re-anchored tests (seat session, cli launcher) and the two N2 tests.
|
||||
# Each new check must fail on a mutant. For each mutant this runs the named test
|
||||
# file against the real source (control) and against the source with one exact-once text edit.
|
||||
# Runs in the given worktree, one test file at a time, and restores every source file on exit.
|
||||
# Usage: mutants.sh <worktree> <outdir>
|
||||
@@ -21,7 +22,7 @@ fs.writeFileSync(f, s.replace(a, () => b));
|
||||
' "$1" "$2" "$3"
|
||||
}
|
||||
run() { # name testfile
|
||||
setsid -w timeout -k 10 240 node --test "$2" > "$OUT/$1.out" 2>&1; echo "$?" > "$OUT/$1.rc"
|
||||
setsid -w timeout -k 10 400 node --test "$2" > "$OUT/$1.out" 2>&1; echo "$?" > "$OUT/$1.rc"
|
||||
echo "$1: exit $(cat "$OUT/$1.rc"); $(grep -E '^ℹ (tests|pass|fail)' "$OUT/$1.out" | tr '\n' ' ')"
|
||||
grep -E '^✖' "$OUT/$1.out" | sort -u | sed 's/ ([0-9.]*ms)$//' | sed "s/^/ failed: /"
|
||||
}
|
||||
@@ -48,3 +49,24 @@ mutant m4-oldest-launch-event "$BT" "$B" "kind IN ('launch.revoked','launch.rest
|
||||
mutant m5-closed-in-open "$BT" "$B" 'const open = this.#tasks(s.business, { done: false });' 'const open = this.#tasks(s.business, { limit: TASK_PAGE });'
|
||||
# m6: the cap isn't stated in the output
|
||||
mutant m6-cap-unstated "$BT" "$B" 'total: open.total, cap: TASK_PAGE }' 'total: open.total }'
|
||||
# Round 3. SE and CL are the seat and cli tests that now anchor the new request shape.
|
||||
SE=packages/seat/tests/session.test.mjs; CL=packages/cli/tests/launcher.test.mjs
|
||||
run control-seat "$SE"
|
||||
run control-cli "$CL"
|
||||
# m7, m9: the turn request no longer carries the state block
|
||||
mutant m7-seat-drop-block "$SE" "$R" 'request: `${block}\n${turnRequest(m)}`' 'request: turnRequest(m)'
|
||||
mutant m9-cli-drop-block "$CL" "$R" 'request: `${block}\n${turnRequest(m)}`' 'request: turnRequest(m)'
|
||||
# m8, m10: the header no longer carries the message time
|
||||
mutant m8-seat-drop-time "$SE" "$R" ' if (m.at) head.push(`sent ${m.at}`);
|
||||
' ''
|
||||
mutant m10-cli-drop-time "$CL" "$R" ' if (m.at) head.push(`sent ${m.at}`);
|
||||
' ''
|
||||
# m11: a human session is checked as an agent, so it can't read state
|
||||
mutant m11-human-checked-as-agent "$BT" "$B" 'if (!s.human && !s.reader) this.#agent(s);
|
||||
const set' 'if (!s.reader) this.#agent(s);
|
||||
const set'
|
||||
# m12: the state read writes an event
|
||||
mutant m12-state-writes-event "$BT" "$B" " case 'state': {
|
||||
keys(a, []);" " case 'state': {
|
||||
keys(a, []);
|
||||
this.#event(s, 'action.allowed', {});"
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
✔ a business that never switched launches reads allowed with no time and no event (12.740463ms)
|
||||
✔ launches revoked and then restored read allowed, with the restore time and event id (10.115322ms)
|
||||
✔ a task closed on the tracker leaves the open list, and total counts the open ones (10.612196ms)
|
||||
✔ a tombstone is not open (7.867781ms)
|
||||
✔ the open list stops at the cap and says so; total still counts every open task (12.014832ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (9.286701ms)
|
||||
ℹ tests 6
|
||||
✔ a business that never switched launches reads allowed with no time and no event (13.404956ms)
|
||||
✔ launches revoked and then restored read allowed, with the restore time and event id (9.927996ms)
|
||||
✔ a task closed on the tracker leaves the open list, and total counts the open ones (10.15205ms)
|
||||
✔ a tombstone is not open (7.75913ms)
|
||||
✔ the open list stops at the cap and says so; total still counts every open task (11.071925ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (8.786717ms)
|
||||
✔ a human session reads the same state, with the same cap (11.494669ms)
|
||||
✔ reading state writes nothing: every table keeps its row count (9.847692ms)
|
||||
ℹ tests 8
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ pass 8
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 117.997122
|
||||
ℹ duration_ms 136.783123
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1174.290121ms)
|
||||
✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (617.784855ms)
|
||||
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or kept and logged (3515.838288ms)
|
||||
✔ a project clone that can't be made refuses checkout-failed, from --pm and from a session, with no run and no clone left (115.118819ms)
|
||||
✔ a runner that stops at once ends its launch with the runner's reason (182.881ms)
|
||||
✔ a Claude Code launch with no session credential refuses before it starts (89.708411ms)
|
||||
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (168.587114ms)
|
||||
✔ a runner killed from the host ends its launch as killed, not failed (268.599875ms)
|
||||
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (115.447284ms)
|
||||
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1260.382887ms)
|
||||
✔ 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 (618.741167ms)
|
||||
✔ 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 (3547.639433ms)
|
||||
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (83.862523ms)
|
||||
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (93.219779ms)
|
||||
✔ a launch client that never closes its side doesn't hold the host's close (90.771484ms)
|
||||
✔ a runner that ignores SIGTERM is killed when the host closes (1206.248141ms)
|
||||
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (4.907571ms)
|
||||
ℹ tests 17
|
||||
ℹ suites 0
|
||||
ℹ pass 17
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 13244.175875
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -1,23 +1,23 @@
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (1.105369ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.21491ms)
|
||||
✔ turnRequest shows the message's own sent time, a day old as well as new (0.126433ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.195396ms)
|
||||
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (152.361989ms)
|
||||
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (169.022648ms)
|
||||
✔ a task closed on the tracker is missing from the next turn's open list (167.718931ms)
|
||||
✔ a message read a day after it was sent shows the time it was sent (87.662171ms)
|
||||
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (63.771972ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (48.50038ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (271.762253ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1192.335593ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (93.322815ms)
|
||||
✔ founder credentials stop before the claim (20) (96.496233ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (100.411332ms)
|
||||
✔ the launch ending under a running session exits 22 (102.903092ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (204.000168ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (47.277428ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (151.489917ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (87.115027ms)
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (0.996076ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.18475ms)
|
||||
✔ turnRequest shows the message's own sent time, a day old as well as new (0.112962ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.167853ms)
|
||||
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (152.682199ms)
|
||||
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (195.236371ms)
|
||||
✔ a task closed on the tracker is missing from the next turn's open list (197.565184ms)
|
||||
✔ a message read a day after it was sent shows the time it was sent (116.074377ms)
|
||||
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (62.720365ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (50.655302ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (246.972742ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1192.859069ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (95.478157ms)
|
||||
✔ founder credentials stop before the claim (20) (107.476052ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (98.628054ms)
|
||||
✔ the launch ending under a running session exits 22 (103.836147ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (216.344891ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (54.307225ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (212.276365ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (111.415632ms)
|
||||
ℹ tests 20
|
||||
ℹ suites 0
|
||||
ℹ pass 20
|
||||
@@ -25,4 +25,4 @@
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 10345.427283
|
||||
ℹ duration_ms 10409.021448
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
✔ family: exactly one launch.max key in the model name, else null (0.688581ms)
|
||||
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.685373ms)
|
||||
✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.249214ms)
|
||||
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.631753ms)
|
||||
✔ session file and launch log: 0600, the session file written once (1.104745ms)
|
||||
✔ endReason maps the runner's exit codes; a signal is killed (0.112483ms)
|
||||
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.227922ms)
|
||||
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (0.955634ms)
|
||||
✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (239.737611ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 9
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 308.976096
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -1,23 +1,23 @@
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (0.927996ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.187137ms)
|
||||
✔ turnRequest shows the message's own sent time, a day old as well as new (0.121749ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.176786ms)
|
||||
✖ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (147.584047ms)
|
||||
✖ launches revoked and then restored: the turn's block reads allowed and gives the restore time (91.206024ms)
|
||||
✖ a task closed on the tracker is missing from the next turn's open list (90.236176ms)
|
||||
✔ a message read a day after it was sent shows the time it was sent (89.018287ms)
|
||||
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (63.256302ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (48.492078ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (247.488656ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1191.072755ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (93.718876ms)
|
||||
✔ founder credentials stop before the claim (20) (94.802529ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (98.777833ms)
|
||||
✔ the launch ending under a running session exits 22 (102.953767ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (206.247136ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (45.3418ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (156.779329ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (83.562291ms)
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (1.137045ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.168832ms)
|
||||
✔ turnRequest shows the message's own sent time, a day old as well as new (0.120972ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.174808ms)
|
||||
✖ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (174.603573ms)
|
||||
✖ launches revoked and then restored: the turn's block reads allowed and gives the restore time (116.168918ms)
|
||||
✖ a task closed on the tracker is missing from the next turn's open list (89.509323ms)
|
||||
✔ a message read a day after it was sent shows the time it was sent (90.617732ms)
|
||||
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (61.927448ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (52.360053ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (303.423394ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1194.4216ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (94.499492ms)
|
||||
✔ founder credentials stop before the claim (20) (109.528158ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (117.959094ms)
|
||||
✔ the launch ending under a running session exits 22 (69.710526ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (210.468074ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (52.84989ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (170.090598ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (92.799913ms)
|
||||
ℹ tests 20
|
||||
ℹ suites 0
|
||||
ℹ pass 17
|
||||
@@ -25,15 +25,15 @@
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 10156.567034
|
||||
ℹ duration_ms 10277.427019
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/harness/tests/runner.test.mjs:174:1
|
||||
✖ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (147.584047ms)
|
||||
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /^echo: Current state, read at \S+:\nLaunches: allowed \(never switched\)\.\nOpen tasks: 0\.\n\nMessage 0d34f793-bec7-4d76-9425-fb1079503a1b from pm, sent 2026-10-13T23:07:58.732Z, class REQUEST:\n\nhello$/. Input:
|
||||
✖ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (174.603573ms)
|
||||
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /^echo: Current state, read at \S+:\nLaunches: allowed \(never switched\)\.\nOpen tasks: 0\.\n\nMessage 00a67a8f-45ba-4623-9be5-905bab8f12c8 from pm, sent 2026-10-13T23:46:08.188Z, class REQUEST:\n\nhello$/. Input:
|
||||
|
||||
'echo: Message 0d34f793-bec7-4d76-9425-fb1079503a1b from pm, sent 2026-10-13T23:07:58.732Z, class REQUEST:\n' +
|
||||
'echo: Message 00a67a8f-45ba-4623-9be5-905bab8f12c8 from pm, sent 2026-10-13T23:46:08.188Z, class REQUEST:\n' +
|
||||
'\n' +
|
||||
'hello'
|
||||
|
||||
@@ -42,17 +42,17 @@ test at packages/harness/tests/runner.test.mjs:174:1
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'echo: Message 0d34f793-bec7-4d76-9425-fb1079503a1b from pm, sent 2026-10-13T23:07:58.732Z, class REQUEST:\n\nhello',
|
||||
expected: /^echo: Current state, read at \S+:\nLaunches: allowed \(never switched\)\.\nOpen tasks: 0\.\n\nMessage 0d34f793-bec7-4d76-9425-fb1079503a1b from pm, sent 2026-10-13T23:07:58.732Z, class REQUEST:\n\nhello$/,
|
||||
actual: 'echo: Message 00a67a8f-45ba-4623-9be5-905bab8f12c8 from pm, sent 2026-10-13T23:46:08.188Z, class REQUEST:\n\nhello',
|
||||
expected: /^echo: Current state, read at \S+:\nLaunches: allowed \(never switched\)\.\nOpen tasks: 0\.\n\nMessage 00a67a8f-45ba-4623-9be5-905bab8f12c8 from pm, sent 2026-10-13T23:46:08.188Z, class REQUEST:\n\nhello$/,
|
||||
operator: 'match',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/harness/tests/runner.test.mjs:217:1
|
||||
✖ launches revoked and then restored: the turn's block reads allowed and gives the restore time (91.206024ms)
|
||||
✖ launches revoked and then restored: the turn's block reads allowed and gives the restore time (116.168918ms)
|
||||
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /\nLaunches: revoked since \S+ \(event \S+\)\.\n/. Input:
|
||||
|
||||
'echo: Message 4fe85a96-a724-4368-a91f-9a7c6dfb1888 from pm, sent 2026-10-13T23:07:58.825Z, class REQUEST:\n' +
|
||||
'echo: Message fa8dd666-e6cb-4988-a47e-3f9cae516271 from pm, sent 2026-10-13T23:46:08.308Z, class REQUEST:\n' +
|
||||
'\n' +
|
||||
'what now'
|
||||
|
||||
@@ -61,17 +61,17 @@ test at packages/harness/tests/runner.test.mjs:217:1
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'echo: Message 4fe85a96-a724-4368-a91f-9a7c6dfb1888 from pm, sent 2026-10-13T23:07:58.825Z, class REQUEST:\n\nwhat now',
|
||||
actual: 'echo: Message fa8dd666-e6cb-4988-a47e-3f9cae516271 from pm, sent 2026-10-13T23:46:08.308Z, class REQUEST:\n\nwhat now',
|
||||
expected: /\nLaunches: revoked since \S+ \(event \S+\)\.\n/,
|
||||
operator: 'match',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/harness/tests/runner.test.mjs:229:1
|
||||
✖ a task closed on the tracker is missing from the next turn's open list (90.236176ms)
|
||||
✖ a task closed on the tracker is missing from the next turn's open list (89.509323ms)
|
||||
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /\nOpen tasks: 2\.\n- vikunja:3\/1 Task 1\n- vikunja:3\/2 Task 2\n/. Input:
|
||||
|
||||
'echo: Message a189d6e4-745d-49d2-9ab6-60e13bf1b5e6 from pm, sent 2026-10-13T23:07:58.914Z, class REQUEST:\n' +
|
||||
'echo: Message 46414e04-1771-4009-9ff4-0306b0ac1e3b from pm, sent 2026-10-13T23:46:08.422Z, class REQUEST:\n' +
|
||||
'\n' +
|
||||
'what now'
|
||||
|
||||
@@ -80,7 +80,7 @@ test at packages/harness/tests/runner.test.mjs:229:1
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'echo: Message a189d6e4-745d-49d2-9ab6-60e13bf1b5e6 from pm, sent 2026-10-13T23:07:58.914Z, class REQUEST:\n\nwhat now',
|
||||
actual: 'echo: Message 46414e04-1771-4009-9ff4-0306b0ac1e3b from pm, sent 2026-10-13T23:46:08.422Z, class REQUEST:\n\nwhat now',
|
||||
expected: /\nOpen tasks: 2\.\n- vikunja:3\/1 Task 1\n- vikunja:3\/2 Task 2\n/,
|
||||
operator: 'match',
|
||||
diff: 'simple'
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1185.643301ms)
|
||||
✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (419.041402ms)
|
||||
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or kept and logged (3521.897462ms)
|
||||
✔ a project clone that can't be made refuses checkout-failed, from --pm and from a session, with no run and no clone left (118.037756ms)
|
||||
✔ a runner that stops at once ends its launch with the runner's reason (183.467491ms)
|
||||
✔ a Claude Code launch with no session credential refuses before it starts (89.091339ms)
|
||||
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (174.240473ms)
|
||||
✔ a runner killed from the host ends its launch as killed, not failed (230.259502ms)
|
||||
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (99.750813ms)
|
||||
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1296.152193ms)
|
||||
✔ 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 (622.836685ms)
|
||||
✔ 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 (3577.584309ms)
|
||||
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (86.170085ms)
|
||||
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (94.36663ms)
|
||||
✔ a launch client that never closes its side doesn't hold the host's close (97.164279ms)
|
||||
✔ a runner that ignores SIGTERM is killed when the host closes (1203.771448ms)
|
||||
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (5.069696ms)
|
||||
ℹ tests 17
|
||||
ℹ suites 0
|
||||
ℹ pass 16
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 13101.787457
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/launcher.test.mjs:203:1
|
||||
✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (419.041402ms)
|
||||
AssertionError [ERR_ASSERTION]: echo: Current state, read at 2026-10-13T23:47:08.981Z:
|
||||
Launches: allowed (never switched).
|
||||
Open tasks: 0.
|
||||
|
||||
Message 28a8bbca-de82-41e7-b1b1-bca5289874b9 from cto, class REQUEST:
|
||||
|
||||
hello
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q109/packages/cli/tests/launcher.test.mjs:243:10)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: null,
|
||||
expected: true,
|
||||
operator: '==',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
diff --git a/packages/harness/src/runner.mjs b/packages/harness/src/runner.mjs
|
||||
index 00595542b..57f4b141a 100644
|
||||
--- a/packages/harness/src/runner.mjs
|
||||
+++ b/packages/harness/src/runner.mjs
|
||||
@@ -62,7 +62,9 @@ export function founderCheck(policy, env) {
|
||||
|
||||
// The request text one message becomes.
|
||||
export function turnRequest(m) {
|
||||
- const head = [`Message ${m.id} from ${m.from_role}`, `class ${m.class}`];
|
||||
+ const head = [`Message ${m.id} from ${m.from_role}`];
|
||||
+ // Row 109 (#1603): when the sender sent it, so a message that waited reads as old.
|
||||
+ head.push(`class ${m.class}`);
|
||||
if (m.in_reply_to) head.push(`in reply to ${m.in_reply_to}`);
|
||||
if (m.decision) head.push(`citing decision ${m.decision}`);
|
||||
// Row 90 (#1584): the human input id task_create cites as its request.
|
||||
@@ -70,6 +72,22 @@ export function turnRequest(m) {
|
||||
return `${head.join(", ")}:\n\n${m.body}`;
|
||||
}
|
||||
|
||||
+// Row 109 (#1603): the block at the top of a turn's request. It is what the broker said at `readAt`,
|
||||
+// the truth for launch state and task status over any older message.
|
||||
+export function stateBlock(state, readAt) {
|
||||
+ const { launch, tasks } = state;
|
||||
+ const since = launch.at ? ` since ${launch.at} (event ${launch.event})` : " (never switched)";
|
||||
+ const shown = tasks.open.length < tasks.total ? `, the first ${tasks.open.length} of cap ${tasks.cap}` : "";
|
||||
+ const open = tasks.open.map((t) => `- ${t.task_ref} ${t.title ?? ""}`.trimEnd());
|
||||
+ return [
|
||||
+ `Current state, read at ${readAt}:`,
|
||||
+ `Launches: ${launch.state}${since}.`,
|
||||
+ `Open tasks: ${tasks.total}${shown}.`,
|
||||
+ ...open,
|
||||
+ "",
|
||||
+ ].join("\n");
|
||||
+}
|
||||
+
|
||||
function clip(text) {
|
||||
return text.length > BODY_MAX ? text.slice(0, BODY_MAX) + "\n[answer cut at 32000 characters]" : text;
|
||||
}
|
||||
@@ -339,7 +357,23 @@ export async function main(runDir, { stdin = process.stdin, env = process.env, l
|
||||
}
|
||||
for (const m of messages) {
|
||||
if (stopping) break loop;
|
||||
- const turn = await runTurn({ session, request: turnRequest(m), n: ++n, env: turnEnv, onChild: (c) => (child = c) });
|
||||
+ // Row 109 (#1603): read the state now, not when the message was sent. A failed read fails the turn.
|
||||
+ let block = null;
|
||||
+ let unread = null;
|
||||
+ try {
|
||||
+ block = stateBlock(await client.call("state"), new Date().toISOString());
|
||||
+ } catch (e) {
|
||||
+ if (SESSION_GONE.has(e.code)) {
|
||||
+ log(`runner: session stopped working: ${e.code}`);
|
||||
+ code = EXIT.session;
|
||||
+ break loop;
|
||||
+ }
|
||||
+ unread = `could not read the current state: ${e.code ?? e.message}`;
|
||||
+ }
|
||||
+ ++n;
|
||||
+ const turn = unread
|
||||
+ ? { ok: false, reason: unread }
|
||||
+ : await runTurn({ session, request: `${block}\n${turnRequest(m)}`, n, env: turnEnv, onChild: (c) => (child = c) });
|
||||
if (stopping) break loop;
|
||||
if (!turn.ok) log(`runner: turn ${n} for message ${m.id} failed: ${turn.reason}`);
|
||||
try {
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,50 @@
|
||||
✔ a business that never switched launches reads allowed with no time and no event (12.904691ms)
|
||||
✔ launches revoked and then restored read allowed, with the restore time and event id (10.014029ms)
|
||||
✔ a task closed on the tracker leaves the open list, and total counts the open ones (9.679771ms)
|
||||
✔ a tombstone is not open (7.948633ms)
|
||||
✔ the open list stops at the cap and says so; total still counts every open task (10.265493ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (8.582359ms)
|
||||
✖ a human session reads the same state, with the same cap (14.855159ms)
|
||||
✖ reading state writes nothing: every table keeps its row count (9.69259ms)
|
||||
ℹ tests 8
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 137.436087
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/state.test.mjs:91:1
|
||||
✖ a human session reads the same state, with the same cap (14.855159ms)
|
||||
Error: agent-required
|
||||
at fail (file:///mnt/storage/scratch/researcher/q109/packages/bus/src/broker.mjs:72:9)
|
||||
at #agent (file:///mnt/storage/scratch/researcher/q109/packages/bus/src/broker.mjs:168:18)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q109/packages/bus/src/broker.mjs:1095:35)
|
||||
at file:///mnt/storage/scratch/researcher/q109/packages/bus/src/broker.mjs:659:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q109/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q109/packages/bus/src/broker.mjs:656:26)
|
||||
at state (file:///mnt/storage/scratch/researcher/q109/packages/bus/tests/state.test.mjs:33:44)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q109/packages/bus/tests/state.test.mjs:94:16)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25) {
|
||||
code: 'agent-required'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/state.test.mjs:101:1
|
||||
✖ reading state writes nothing: every table keeps its row count (9.69259ms)
|
||||
Error: agent-required
|
||||
at fail (file:///mnt/storage/scratch/researcher/q109/packages/bus/src/broker.mjs:72:9)
|
||||
at #agent (file:///mnt/storage/scratch/researcher/q109/packages/bus/src/broker.mjs:168:18)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q109/packages/bus/src/broker.mjs:1095:35)
|
||||
at file:///mnt/storage/scratch/researcher/q109/packages/bus/src/broker.mjs:659:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q109/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q109/packages/bus/src/broker.mjs:656:26)
|
||||
at state (file:///mnt/storage/scratch/researcher/q109/packages/bus/tests/state.test.mjs:33:44)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q109/packages/bus/tests/state.test.mjs:111:42)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25) {
|
||||
code: 'agent-required'
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs
|
||||
index 732c7310e..b6be84633 100644
|
||||
--- a/packages/bus/src/broker.mjs
|
||||
+++ b/packages/bus/src/broker.mjs
|
||||
@@ -654,7 +654,7 @@ export class Broker {
|
||||
if (!object(args)) fail('invalid-request');
|
||||
this.#secretCheck(args);
|
||||
return this.#store.transaction(() => {
|
||||
- if (s.reader && !['inbox', 'agents', 'tasks', 'trail', 'decisions', 'credentials', 'replies', 'messages'].includes(request.verb))
|
||||
+ if (s.reader && !['inbox', 'agents', 'tasks', 'trail', 'decisions', 'credentials', 'replies', 'messages', 'state'].includes(request.verb))
|
||||
fail('read-only');
|
||||
const result = this.#dispatch(s, request.verb, args);
|
||||
this.#secretCheck(result);
|
||||
@@ -1086,6 +1086,23 @@ export class Broker {
|
||||
this.#agent(s);
|
||||
return this.#requirements;
|
||||
}
|
||||
+ // Row 109 (#1603): what a run needs to know before it plans. The business's launch state with
|
||||
+ // the time and id of the event that set it (`allowed`, no time and no id when none has), and
|
||||
+ // the open tasks as the tracker snapshot shows them: refs and titles, at most `cap` of them,
|
||||
+ // `total` counting every open task. A read for role, reader and human sessions; nothing is stored.
|
||||
+ case 'state': {
|
||||
+ keys(a, []);
|
||||
+ if (!s.reader) this.#agent(s);
|
||||
+ const set = this.#store.get(
|
||||
+ "SELECT id,kind,at FROM events WHERE business=? AND kind IN ('launch.revoked','launch.restored') ORDER BY seq DESC LIMIT 1",
|
||||
+ s.business,
|
||||
+ );
|
||||
+ const open = this.#tasks(s.business, { done: false });
|
||||
+ return {
|
||||
+ launch: { state: set?.kind === 'launch.revoked' ? 'revoked' : 'allowed', at: set?.at ?? null, event: set?.id ?? null },
|
||||
+ tasks: { open: open.tasks.map(({ task_ref, title }) => ({ task_ref, title })), total: open.total, cap: TASK_PAGE },
|
||||
+ };
|
||||
+ }
|
||||
case 'inbox':
|
||||
case 'agents':
|
||||
case 'tasks':
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,50 @@
|
||||
✔ a business that never switched launches reads allowed with no time and no event (13.193161ms)
|
||||
✔ launches revoked and then restored read allowed, with the restore time and event id (11.903049ms)
|
||||
✔ a task closed on the tracker leaves the open list, and total counts the open ones (11.563246ms)
|
||||
✔ a tombstone is not open (7.947125ms)
|
||||
✔ the open list stops at the cap and says so; total still counts every open task (11.921689ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (9.843509ms)
|
||||
✔ a human session reads the same state, with the same cap (13.151505ms)
|
||||
✖ reading state writes nothing: every table keeps its row count (11.542709ms)
|
||||
ℹ tests 8
|
||||
ℹ suites 0
|
||||
ℹ pass 7
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 151.367996
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/state.test.mjs:101:1
|
||||
✖ reading state writes nothing: every table keeps its row count (11.542709ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
{
|
||||
decision_events: 0,
|
||||
decisions: 0,
|
||||
deliveries: 0,
|
||||
+ events: 6,
|
||||
- events: 3,
|
||||
messages: 0,
|
||||
meta: 2,
|
||||
role_claims: 1,
|
||||
task_snapshots: 2
|
||||
}
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q109/packages/bus/tests/state.test.mjs:112:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: { meta: 2, events: 6, role_claims: 1, decisions: 0, decision_events: 0, messages: 0, deliveries: 0, task_snapshots: 2 },
|
||||
expected: { meta: 2, events: 3, role_claims: 1, decisions: 0, decision_events: 0, messages: 0, deliveries: 0, task_snapshots: 2 },
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs
|
||||
index 732c7310e..49f21cd95 100644
|
||||
--- a/packages/bus/src/broker.mjs
|
||||
+++ b/packages/bus/src/broker.mjs
|
||||
@@ -654,7 +654,7 @@ export class Broker {
|
||||
if (!object(args)) fail('invalid-request');
|
||||
this.#secretCheck(args);
|
||||
return this.#store.transaction(() => {
|
||||
- if (s.reader && !['inbox', 'agents', 'tasks', 'trail', 'decisions', 'credentials', 'replies', 'messages'].includes(request.verb))
|
||||
+ if (s.reader && !['inbox', 'agents', 'tasks', 'trail', 'decisions', 'credentials', 'replies', 'messages', 'state'].includes(request.verb))
|
||||
fail('read-only');
|
||||
const result = this.#dispatch(s, request.verb, args);
|
||||
this.#secretCheck(result);
|
||||
@@ -1086,6 +1086,24 @@ export class Broker {
|
||||
this.#agent(s);
|
||||
return this.#requirements;
|
||||
}
|
||||
+ // Row 109 (#1603): what a run needs to know before it plans. The business's launch state with
|
||||
+ // the time and id of the event that set it (`allowed`, no time and no id when none has), and
|
||||
+ // the open tasks as the tracker snapshot shows them: refs and titles, at most `cap` of them,
|
||||
+ // `total` counting every open task. A read for role, reader and human sessions; nothing is stored.
|
||||
+ case 'state': {
|
||||
+ keys(a, []);
|
||||
+ this.#event(s, 'action.allowed', {});
|
||||
+ if (!s.human && !s.reader) this.#agent(s);
|
||||
+ const set = this.#store.get(
|
||||
+ "SELECT id,kind,at FROM events WHERE business=? AND kind IN ('launch.revoked','launch.restored') ORDER BY seq DESC LIMIT 1",
|
||||
+ s.business,
|
||||
+ );
|
||||
+ const open = this.#tasks(s.business, { done: false });
|
||||
+ return {
|
||||
+ launch: { state: set?.kind === 'launch.revoked' ? 'revoked' : 'allowed', at: set?.at ?? null, event: set?.id ?? null },
|
||||
+ tasks: { open: open.tasks.map(({ task_ref, title }) => ({ task_ref, title })), total: open.total, cap: TASK_PAGE },
|
||||
+ };
|
||||
+ }
|
||||
case 'inbox':
|
||||
case 'agents':
|
||||
case 'tasks':
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -1,23 +1,23 @@
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (0.987417ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.198796ms)
|
||||
✖ turnRequest shows the message's own sent time, a day old as well as new (0.813441ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.188045ms)
|
||||
✖ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (145.435504ms)
|
||||
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (169.165152ms)
|
||||
✔ a task closed on the tracker is missing from the next turn's open list (168.933118ms)
|
||||
✖ a message read a day after it was sent shows the time it was sent (90.269575ms)
|
||||
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (63.164189ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (50.491583ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (272.210562ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1192.639985ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (94.793583ms)
|
||||
✔ founder credentials stop before the claim (20) (93.850655ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (99.075117ms)
|
||||
✔ the launch ending under a running session exits 22 (101.888229ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (204.216765ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (49.593739ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (156.481136ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (91.206713ms)
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (0.931781ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.18307ms)
|
||||
✖ turnRequest shows the message's own sent time, a day old as well as new (0.74434ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.18371ms)
|
||||
✖ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (148.21525ms)
|
||||
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (199.168531ms)
|
||||
✔ a task closed on the tracker is missing from the next turn's open list (177.221273ms)
|
||||
✖ a message read a day after it was sent shows the time it was sent (122.551498ms)
|
||||
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (69.582978ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (48.980837ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (274.310184ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1194.311039ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (98.243737ms)
|
||||
✔ founder credentials stop before the claim (20) (108.030503ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (97.564566ms)
|
||||
✔ the launch ending under a running session exits 22 (100.653555ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (201.544671ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (52.123789ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (160.929057ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (85.710098ms)
|
||||
ℹ tests 20
|
||||
ℹ suites 0
|
||||
ℹ pass 17
|
||||
@@ -25,12 +25,12 @@
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 10339.878414
|
||||
ℹ duration_ms 10426.692073
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/harness/tests/runner.test.mjs:60:1
|
||||
✖ turnRequest shows the message's own sent time, a day old as well as new (0.813441ms)
|
||||
✖ turnRequest shows the message's own sent time, a day old as well as new (0.74434ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
+ actual - expected
|
||||
|
||||
@@ -53,14 +53,14 @@ test at packages/harness/tests/runner.test.mjs:60:1
|
||||
}
|
||||
|
||||
test at packages/harness/tests/runner.test.mjs:174:1
|
||||
✖ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (145.435504ms)
|
||||
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /^echo: Current state, read at \S+:\nLaunches: allowed \(never switched\)\.\nOpen tasks: 0\.\n\nMessage 2b684cd9-8412-4498-9700-651ca8840067 from pm, sent 2026-10-13T23:08:08.949Z, class REQUEST:\n\nhello$/. Input:
|
||||
✖ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (148.21525ms)
|
||||
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /^echo: Current state, read at \S+:\nLaunches: allowed \(never switched\)\.\nOpen tasks: 0\.\n\nMessage c9a491b1-36dc-456b-846b-f7d45e572aa2 from pm, sent 2026-10-13T23:46:18.514Z, class REQUEST:\n\nhello$/. Input:
|
||||
|
||||
'echo: Current state, read at 2026-10-13T23:08:08.986Z:\n' +
|
||||
'echo: Current state, read at 2026-10-13T23:46:18.552Z:\n' +
|
||||
'Launches: allowed (never switched).\n' +
|
||||
'Open tasks: 0.\n' +
|
||||
'\n' +
|
||||
'Message 2b684cd9-8412-4498-9700-651ca8840067 from pm, class REQUEST:\n' +
|
||||
'Message c9a491b1-36dc-456b-846b-f7d45e572aa2 from pm, class REQUEST:\n' +
|
||||
'\n' +
|
||||
'hello'
|
||||
|
||||
@@ -69,14 +69,14 @@ test at packages/harness/tests/runner.test.mjs:174:1
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'echo: Current state, read at 2026-10-13T23:08:08.986Z:\nLaunches: allowed (never switched).\nOpen tasks: 0.\n\nMessage 2b684cd9-8412-4498-9700-651ca8840067 from pm, class REQUEST:\n\nhello',
|
||||
expected: /^echo: Current state, read at \S+:\nLaunches: allowed \(never switched\)\.\nOpen tasks: 0\.\n\nMessage 2b684cd9-8412-4498-9700-651ca8840067 from pm, sent 2026-10-13T23:08:08.949Z, class REQUEST:\n\nhello$/,
|
||||
actual: 'echo: Current state, read at 2026-10-13T23:46:18.552Z:\nLaunches: allowed (never switched).\nOpen tasks: 0.\n\nMessage c9a491b1-36dc-456b-846b-f7d45e572aa2 from pm, class REQUEST:\n\nhello',
|
||||
expected: /^echo: Current state, read at \S+:\nLaunches: allowed \(never switched\)\.\nOpen tasks: 0\.\n\nMessage c9a491b1-36dc-456b-846b-f7d45e572aa2 from pm, sent 2026-10-13T23:46:18.514Z, class REQUEST:\n\nhello$/,
|
||||
operator: 'match',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/harness/tests/runner.test.mjs:243:1
|
||||
✖ a message read a day after it was sent shows the time it was sent (90.269575ms)
|
||||
✖ a message read a day after it was sent shows the time it was sent (122.551498ms)
|
||||
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
|
||||
|
||||
assert.ok(reply.body.includes(`Message ${sent.id} from pm, sent ${sentAt}, class REQUEST:`))
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (1.027678ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.175568ms)
|
||||
✔ turnRequest shows the message's own sent time, a day old as well as new (0.108091ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.164473ms)
|
||||
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (151.370525ms)
|
||||
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (168.631533ms)
|
||||
✔ a task closed on the tracker is missing from the next turn's open list (193.462068ms)
|
||||
✔ a message read a day after it was sent shows the time it was sent (88.274542ms)
|
||||
✖ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (89.644081ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (50.931607ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (273.817356ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1189.440166ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (95.175856ms)
|
||||
✔ founder credentials stop before the claim (20) (89.610808ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (96.280534ms)
|
||||
✔ the launch ending under a running session exits 22 (100.621249ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (202.570602ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (49.247418ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (161.38161ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (86.000718ms)
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (1.124357ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.192085ms)
|
||||
✔ turnRequest shows the message's own sent time, a day old as well as new (0.157336ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.326969ms)
|
||||
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (151.349748ms)
|
||||
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (194.278473ms)
|
||||
✔ a task closed on the tracker is missing from the next turn's open list (173.243486ms)
|
||||
✔ a message read a day after it was sent shows the time it was sent (91.302615ms)
|
||||
✖ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (92.474404ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (50.856577ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (302.583008ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1190.99392ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (94.851143ms)
|
||||
✔ founder credentials stop before the claim (20) (105.928087ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (99.145876ms)
|
||||
✔ the launch ending under a running session exits 22 (102.024504ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (201.993852ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (60.300586ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (169.240459ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (91.004933ms)
|
||||
ℹ tests 20
|
||||
ℹ suites 0
|
||||
ℹ pass 19
|
||||
@@ -25,17 +25,17 @@
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 10397.655899
|
||||
ℹ duration_ms 10443.553072
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/harness/tests/runner.test.mjs:259:1
|
||||
✖ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (89.644081ms)
|
||||
✖ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (92.474404ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
+ actual - expected
|
||||
|
||||
+ 'echo: \n' +
|
||||
+ 'Message 586075ac-c0cc-4abf-9f4f-2d1289199157 from pm, sent 2026-10-13T23:08:19.896Z, class REQUEST:\n' +
|
||||
+ 'Message 723f87fa-c43f-4bbf-afab-2dc8fba92b78 from pm, sent 2026-10-13T23:46:29.561Z, class REQUEST:\n' +
|
||||
+ '\n' +
|
||||
+ 'plan the week'
|
||||
- 'The turn failed: could not read the current state: state-unavailable'
|
||||
@@ -45,7 +45,7 @@ test at packages/harness/tests/runner.test.mjs:259:1
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'echo: \nMessage 586075ac-c0cc-4abf-9f4f-2d1289199157 from pm, sent 2026-10-13T23:08:19.896Z, class REQUEST:\n\nplan the week',
|
||||
actual: 'echo: \nMessage 723f87fa-c43f-4bbf-afab-2dc8fba92b78 from pm, sent 2026-10-13T23:46:29.561Z, class REQUEST:\n\nplan the week',
|
||||
expected: 'The turn failed: could not read the current state: state-unavailable',
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
✔ a business that never switched launches reads allowed with no time and no event (11.927647ms)
|
||||
✖ launches revoked and then restored read allowed, with the restore time and event id (10.621051ms)
|
||||
✔ a task closed on the tracker leaves the open list, and total counts the open ones (9.618177ms)
|
||||
✔ a tombstone is not open (7.741209ms)
|
||||
✔ the open list stops at the cap and says so; total still counts every open task (11.030111ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (8.605084ms)
|
||||
ℹ tests 6
|
||||
✔ a business that never switched launches reads allowed with no time and no event (12.185745ms)
|
||||
✖ launches revoked and then restored read allowed, with the restore time and event id (9.967375ms)
|
||||
✔ a task closed on the tracker leaves the open list, and total counts the open ones (9.646682ms)
|
||||
✔ a tombstone is not open (7.717991ms)
|
||||
✔ the open list stops at the cap and says so; total still counts every open task (10.839838ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (8.967246ms)
|
||||
✔ a human session reads the same state, with the same cap (10.814814ms)
|
||||
✔ reading state writes nothing: every table keeps its row count (9.592403ms)
|
||||
ℹ tests 8
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ pass 7
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 112.662835
|
||||
ℹ duration_ms 134.620599
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/state.test.mjs:42:1
|
||||
✖ launches revoked and then restored read allowed, with the restore time and event id (10.621051ms)
|
||||
✖ launches revoked and then restored read allowed, with the restore time and event id (9.967375ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
{
|
||||
+ at: '2026-10-13T23:08:29.735Z',
|
||||
+ event: '03630f89-6d18-4f43-8ced-8f36a81f8fbc',
|
||||
+ at: '2026-10-13T23:46:39.436Z',
|
||||
+ event: 'c5c62f60-4cd9-4760-924d-003f7340b130',
|
||||
+ state: 'revoked'
|
||||
- at: '2026-10-13T23:08:29.737Z',
|
||||
- event: 'f2c5df5c-6b5b-4f25-91ca-2137aa67912d',
|
||||
- at: '2026-10-13T23:46:39.438Z',
|
||||
- event: '8f0385f2-ade4-44d4-97a6-7cced43a0da7',
|
||||
- state: 'allowed'
|
||||
}
|
||||
|
||||
@@ -38,8 +40,8 @@ test at packages/bus/tests/state.test.mjs:42:1
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: { state: 'revoked', at: '2026-10-13T23:08:29.735Z', event: '03630f89-6d18-4f43-8ced-8f36a81f8fbc' },
|
||||
expected: { state: 'allowed', at: '2026-10-13T23:08:29.737Z', event: 'f2c5df5c-6b5b-4f25-91ca-2137aa67912d' },
|
||||
actual: { state: 'revoked', at: '2026-10-13T23:46:39.436Z', event: 'c5c62f60-4cd9-4760-924d-003f7340b130' },
|
||||
expected: { state: 'allowed', at: '2026-10-13T23:46:39.438Z', event: '8f0385f2-ade4-44d4-97a6-7cced43a0da7' },
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs
|
||||
index 732c7310e..054b7acff 100644
|
||||
index 732c7310e..e2be940a7 100644
|
||||
--- a/packages/bus/src/broker.mjs
|
||||
+++ b/packages/bus/src/broker.mjs
|
||||
@@ -654,7 +654,7 @@ export class Broker {
|
||||
@@ -18,7 +18,7 @@ index 732c7310e..054b7acff 100644
|
||||
+ // Row 109 (#1603): what a run needs to know before it plans. The business's launch state with
|
||||
+ // the time and id of the event that set it (`allowed`, no time and no id when none has), and
|
||||
+ // the open tasks as the tracker snapshot shows them: refs and titles, at most `cap` of them,
|
||||
+ // `total` counting every open task. A read for role and reader sessions; nothing is stored.
|
||||
+ // `total` counting every open task. A read for role, reader and human sessions; nothing is stored.
|
||||
+ case 'state': {
|
||||
+ keys(a, []);
|
||||
+ if (!s.human && !s.reader) this.#agent(s);
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
✔ a business that never switched launches reads allowed with no time and no event (11.379419ms)
|
||||
✔ launches revoked and then restored read allowed, with the restore time and event id (9.235032ms)
|
||||
✖ a task closed on the tracker leaves the open list, and total counts the open ones (10.543996ms)
|
||||
✔ a tombstone is not open (8.500886ms)
|
||||
✔ the open list stops at the cap and says so; total still counts every open task (11.900535ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (8.938047ms)
|
||||
ℹ tests 6
|
||||
✔ a business that never switched launches reads allowed with no time and no event (13.482215ms)
|
||||
✔ launches revoked and then restored read allowed, with the restore time and event id (9.948579ms)
|
||||
✖ a task closed on the tracker leaves the open list, and total counts the open ones (11.062696ms)
|
||||
✔ a tombstone is not open (7.706973ms)
|
||||
✔ the open list stops at the cap and says so; total still counts every open task (11.48627ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (9.203292ms)
|
||||
✔ a human session reads the same state, with the same cap (11.518095ms)
|
||||
✔ reading state writes nothing: every table keeps its row count (9.601779ms)
|
||||
ℹ tests 8
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ pass 7
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 117.78508
|
||||
ℹ duration_ms 139.39966
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/state.test.mjs:55:1
|
||||
✖ a task closed on the tracker leaves the open list, and total counts the open ones (10.543996ms)
|
||||
✖ a task closed on the tracker leaves the open list, and total counts the open ones (11.062696ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs
|
||||
index 732c7310e..138991706 100644
|
||||
index 732c7310e..5682bf558 100644
|
||||
--- a/packages/bus/src/broker.mjs
|
||||
+++ b/packages/bus/src/broker.mjs
|
||||
@@ -654,7 +654,7 @@ export class Broker {
|
||||
@@ -18,7 +18,7 @@ index 732c7310e..138991706 100644
|
||||
+ // Row 109 (#1603): what a run needs to know before it plans. The business's launch state with
|
||||
+ // the time and id of the event that set it (`allowed`, no time and no id when none has), and
|
||||
+ // the open tasks as the tracker snapshot shows them: refs and titles, at most `cap` of them,
|
||||
+ // `total` counting every open task. A read for role and reader sessions; nothing is stored.
|
||||
+ // `total` counting every open task. A read for role, reader and human sessions; nothing is stored.
|
||||
+ case 'state': {
|
||||
+ keys(a, []);
|
||||
+ if (!s.human && !s.reader) this.#agent(s);
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
✔ a business that never switched launches reads allowed with no time and no event (11.422277ms)
|
||||
✔ launches revoked and then restored read allowed, with the restore time and event id (8.798469ms)
|
||||
✖ a task closed on the tracker leaves the open list, and total counts the open ones (9.437594ms)
|
||||
✔ a tombstone is not open (7.259551ms)
|
||||
✖ the open list stops at the cap and says so; total still counts every open task (10.864472ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (7.816773ms)
|
||||
ℹ tests 6
|
||||
✔ a business that never switched launches reads allowed with no time and no event (12.633789ms)
|
||||
✔ launches revoked and then restored read allowed, with the restore time and event id (9.403191ms)
|
||||
✖ a task closed on the tracker leaves the open list, and total counts the open ones (9.691802ms)
|
||||
✔ a tombstone is not open (7.986306ms)
|
||||
✖ the open list stops at the cap and says so; total still counts every open task (10.999378ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (8.575416ms)
|
||||
✖ a human session reads the same state, with the same cap (11.491233ms)
|
||||
✔ reading state writes nothing: every table keeps its row count (9.263729ms)
|
||||
ℹ tests 8
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 2
|
||||
ℹ pass 5
|
||||
ℹ fail 3
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 110.771289
|
||||
ℹ duration_ms 135.758186
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/state.test.mjs:55:1
|
||||
✖ a task closed on the tracker leaves the open list, and total counts the open ones (9.437594ms)
|
||||
✖ a task closed on the tracker leaves the open list, and total counts the open ones (9.691802ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
@@ -44,7 +46,7 @@ test at packages/bus/tests/state.test.mjs:55:1
|
||||
}
|
||||
|
||||
test at packages/bus/tests/state.test.mjs:75:1
|
||||
✖ the open list stops at the cap and says so; total still counts every open task (10.864472ms)
|
||||
✖ the open list stops at the cap and says so; total still counts every open task (10.999378ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
undefined !== 50
|
||||
@@ -63,3 +65,24 @@ test at packages/bus/tests/state.test.mjs:75:1
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/state.test.mjs:91:1
|
||||
✖ a human session reads the same state, with the same cap (11.491233ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
undefined !== 50
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q109/packages/bus/tests/state.test.mjs:96:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: 50,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs
|
||||
index 732c7310e..6df80db83 100644
|
||||
index 732c7310e..e15d76679 100644
|
||||
--- a/packages/bus/src/broker.mjs
|
||||
+++ b/packages/bus/src/broker.mjs
|
||||
@@ -654,7 +654,7 @@ export class Broker {
|
||||
@@ -18,7 +18,7 @@ index 732c7310e..6df80db83 100644
|
||||
+ // Row 109 (#1603): what a run needs to know before it plans. The business's launch state with
|
||||
+ // the time and id of the event that set it (`allowed`, no time and no id when none has), and
|
||||
+ // the open tasks as the tracker snapshot shows them: refs and titles, at most `cap` of them,
|
||||
+ // `total` counting every open task. A read for role and reader sessions; nothing is stored.
|
||||
+ // `total` counting every open task. A read for role, reader and human sessions; nothing is stored.
|
||||
+ case 'state': {
|
||||
+ keys(a, []);
|
||||
+ if (!s.human && !s.reader) this.#agent(s);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
✔ family: exactly one launch.max key in the model name, else null (0.69757ms)
|
||||
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.725919ms)
|
||||
✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.260472ms)
|
||||
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.620341ms)
|
||||
✔ session file and launch log: 0600, the session file written once (1.147556ms)
|
||||
✔ endReason maps the runner's exit codes; a signal is killed (0.126274ms)
|
||||
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.30607ms)
|
||||
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (0.982977ms)
|
||||
✖ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (139.851597ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 8
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 211.382544
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/seat/tests/session.test.mjs:152:1
|
||||
✖ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (139.851597ms)
|
||||
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /^echo: Current state, read at \S+:\nLaunches: allowed \(never switched\)\.\nOpen tasks: 0\.\n\nMessage eb9b2e24-416c-4dad-b8d6-ccb0e23ed00d from pm, sent 2026-10-13T23:46:53.680Z, class REQUEST:\n\nhello$/. Input:
|
||||
|
||||
'echo: Message eb9b2e24-416c-4dad-b8d6-ccb0e23ed00d from pm, sent 2026-10-13T23:46:53.680Z, class REQUEST:\n' +
|
||||
'\n' +
|
||||
'hello'
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q109/packages/seat/tests/session.test.mjs:229:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'echo: Message eb9b2e24-416c-4dad-b8d6-ccb0e23ed00d from pm, sent 2026-10-13T23:46:53.680Z, class REQUEST:\n\nhello',
|
||||
expected: /^echo: Current state, read at \S+:\nLaunches: allowed \(never switched\)\.\nOpen tasks: 0\.\n\nMessage eb9b2e24-416c-4dad-b8d6-ccb0e23ed00d from pm, sent 2026-10-13T23:46:53.680Z, class REQUEST:\n\nhello$/,
|
||||
operator: 'match',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
diff --git a/packages/harness/src/runner.mjs b/packages/harness/src/runner.mjs
|
||||
index 00595542b..87d66dd36 100644
|
||||
--- a/packages/harness/src/runner.mjs
|
||||
+++ b/packages/harness/src/runner.mjs
|
||||
@@ -62,7 +62,10 @@ export function founderCheck(policy, env) {
|
||||
|
||||
// The request text one message becomes.
|
||||
export function turnRequest(m) {
|
||||
- const head = [`Message ${m.id} from ${m.from_role}`, `class ${m.class}`];
|
||||
+ const head = [`Message ${m.id} from ${m.from_role}`];
|
||||
+ // Row 109 (#1603): when the sender sent it, so a message that waited reads as old.
|
||||
+ if (m.at) head.push(`sent ${m.at}`);
|
||||
+ head.push(`class ${m.class}`);
|
||||
if (m.in_reply_to) head.push(`in reply to ${m.in_reply_to}`);
|
||||
if (m.decision) head.push(`citing decision ${m.decision}`);
|
||||
// Row 90 (#1584): the human input id task_create cites as its request.
|
||||
@@ -70,6 +73,22 @@ export function turnRequest(m) {
|
||||
return `${head.join(", ")}:\n\n${m.body}`;
|
||||
}
|
||||
|
||||
+// Row 109 (#1603): the block at the top of a turn's request. It is what the broker said at `readAt`,
|
||||
+// the truth for launch state and task status over any older message.
|
||||
+export function stateBlock(state, readAt) {
|
||||
+ const { launch, tasks } = state;
|
||||
+ const since = launch.at ? ` since ${launch.at} (event ${launch.event})` : " (never switched)";
|
||||
+ const shown = tasks.open.length < tasks.total ? `, the first ${tasks.open.length} of cap ${tasks.cap}` : "";
|
||||
+ const open = tasks.open.map((t) => `- ${t.task_ref} ${t.title ?? ""}`.trimEnd());
|
||||
+ return [
|
||||
+ `Current state, read at ${readAt}:`,
|
||||
+ `Launches: ${launch.state}${since}.`,
|
||||
+ `Open tasks: ${tasks.total}${shown}.`,
|
||||
+ ...open,
|
||||
+ "",
|
||||
+ ].join("\n");
|
||||
+}
|
||||
+
|
||||
function clip(text) {
|
||||
return text.length > BODY_MAX ? text.slice(0, BODY_MAX) + "\n[answer cut at 32000 characters]" : text;
|
||||
}
|
||||
@@ -339,7 +358,23 @@ export async function main(runDir, { stdin = process.stdin, env = process.env, l
|
||||
}
|
||||
for (const m of messages) {
|
||||
if (stopping) break loop;
|
||||
- const turn = await runTurn({ session, request: turnRequest(m), n: ++n, env: turnEnv, onChild: (c) => (child = c) });
|
||||
+ // Row 109 (#1603): read the state now, not when the message was sent. A failed read fails the turn.
|
||||
+ let block = null;
|
||||
+ let unread = null;
|
||||
+ try {
|
||||
+ block = stateBlock(await client.call("state"), new Date().toISOString());
|
||||
+ } catch (e) {
|
||||
+ if (SESSION_GONE.has(e.code)) {
|
||||
+ log(`runner: session stopped working: ${e.code}`);
|
||||
+ code = EXIT.session;
|
||||
+ break loop;
|
||||
+ }
|
||||
+ unread = `could not read the current state: ${e.code ?? e.message}`;
|
||||
+ }
|
||||
+ ++n;
|
||||
+ const turn = unread
|
||||
+ ? { ok: false, reason: unread }
|
||||
+ : await runTurn({ session, request: turnRequest(m), n, env: turnEnv, onChild: (c) => (child = c) });
|
||||
if (stopping) break loop;
|
||||
if (!turn.ok) log(`runner: turn ${n} for message ${m.id} failed: ${turn.reason}`);
|
||||
try {
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,42 @@
|
||||
✔ family: exactly one launch.max key in the model name, else null (0.733731ms)
|
||||
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.805215ms)
|
||||
✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.282759ms)
|
||||
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.731ms)
|
||||
✔ session file and launch log: 0600, the session file written once (1.199904ms)
|
||||
✔ endReason maps the runner's exit codes; a signal is killed (0.125987ms)
|
||||
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.390735ms)
|
||||
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (1.048275ms)
|
||||
✖ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (140.396492ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 8
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 211.096894
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/seat/tests/session.test.mjs:152:1
|
||||
✖ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (140.396492ms)
|
||||
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /^echo: Current state, read at \S+:\nLaunches: allowed \(never switched\)\.\nOpen tasks: 0\.\n\nMessage b33a7c4d-931f-4a01-8670-d3a25c4ff1dc from pm, sent 2026-10-13T23:47:07.180Z, class REQUEST:\n\nhello$/. Input:
|
||||
|
||||
'echo: Current state, read at 2026-10-13T23:47:07.204Z:\n' +
|
||||
'Launches: allowed (never switched).\n' +
|
||||
'Open tasks: 0.\n' +
|
||||
'\n' +
|
||||
'Message b33a7c4d-931f-4a01-8670-d3a25c4ff1dc from pm, class REQUEST:\n' +
|
||||
'\n' +
|
||||
'hello'
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q109/packages/seat/tests/session.test.mjs:229:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'echo: Current state, read at 2026-10-13T23:47:07.204Z:\nLaunches: allowed (never switched).\nOpen tasks: 0.\n\nMessage b33a7c4d-931f-4a01-8670-d3a25c4ff1dc from pm, class REQUEST:\n\nhello',
|
||||
expected: /^echo: Current state, read at \S+:\nLaunches: allowed \(never switched\)\.\nOpen tasks: 0\.\n\nMessage b33a7c4d-931f-4a01-8670-d3a25c4ff1dc from pm, sent 2026-10-13T23:47:07.180Z, class REQUEST:\n\nhello$/,
|
||||
operator: 'match',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
diff --git a/packages/harness/src/runner.mjs b/packages/harness/src/runner.mjs
|
||||
index 00595542b..57f4b141a 100644
|
||||
--- a/packages/harness/src/runner.mjs
|
||||
+++ b/packages/harness/src/runner.mjs
|
||||
@@ -62,7 +62,9 @@ export function founderCheck(policy, env) {
|
||||
|
||||
// The request text one message becomes.
|
||||
export function turnRequest(m) {
|
||||
- const head = [`Message ${m.id} from ${m.from_role}`, `class ${m.class}`];
|
||||
+ const head = [`Message ${m.id} from ${m.from_role}`];
|
||||
+ // Row 109 (#1603): when the sender sent it, so a message that waited reads as old.
|
||||
+ head.push(`class ${m.class}`);
|
||||
if (m.in_reply_to) head.push(`in reply to ${m.in_reply_to}`);
|
||||
if (m.decision) head.push(`citing decision ${m.decision}`);
|
||||
// Row 90 (#1584): the human input id task_create cites as its request.
|
||||
@@ -70,6 +72,22 @@ export function turnRequest(m) {
|
||||
return `${head.join(", ")}:\n\n${m.body}`;
|
||||
}
|
||||
|
||||
+// Row 109 (#1603): the block at the top of a turn's request. It is what the broker said at `readAt`,
|
||||
+// the truth for launch state and task status over any older message.
|
||||
+export function stateBlock(state, readAt) {
|
||||
+ const { launch, tasks } = state;
|
||||
+ const since = launch.at ? ` since ${launch.at} (event ${launch.event})` : " (never switched)";
|
||||
+ const shown = tasks.open.length < tasks.total ? `, the first ${tasks.open.length} of cap ${tasks.cap}` : "";
|
||||
+ const open = tasks.open.map((t) => `- ${t.task_ref} ${t.title ?? ""}`.trimEnd());
|
||||
+ return [
|
||||
+ `Current state, read at ${readAt}:`,
|
||||
+ `Launches: ${launch.state}${since}.`,
|
||||
+ `Open tasks: ${tasks.total}${shown}.`,
|
||||
+ ...open,
|
||||
+ "",
|
||||
+ ].join("\n");
|
||||
+}
|
||||
+
|
||||
function clip(text) {
|
||||
return text.length > BODY_MAX ? text.slice(0, BODY_MAX) + "\n[answer cut at 32000 characters]" : text;
|
||||
}
|
||||
@@ -339,7 +357,23 @@ export async function main(runDir, { stdin = process.stdin, env = process.env, l
|
||||
}
|
||||
for (const m of messages) {
|
||||
if (stopping) break loop;
|
||||
- const turn = await runTurn({ session, request: turnRequest(m), n: ++n, env: turnEnv, onChild: (c) => (child = c) });
|
||||
+ // Row 109 (#1603): read the state now, not when the message was sent. A failed read fails the turn.
|
||||
+ let block = null;
|
||||
+ let unread = null;
|
||||
+ try {
|
||||
+ block = stateBlock(await client.call("state"), new Date().toISOString());
|
||||
+ } catch (e) {
|
||||
+ if (SESSION_GONE.has(e.code)) {
|
||||
+ log(`runner: session stopped working: ${e.code}`);
|
||||
+ code = EXIT.session;
|
||||
+ break loop;
|
||||
+ }
|
||||
+ unread = `could not read the current state: ${e.code ?? e.message}`;
|
||||
+ }
|
||||
+ ++n;
|
||||
+ const turn = unread
|
||||
+ ? { ok: false, reason: unread }
|
||||
+ : await runTurn({ session, request: `${block}\n${turnRequest(m)}`, n, env: turnEnv, onChild: (c) => (child = c) });
|
||||
if (stopping) break loop;
|
||||
if (!turn.ok) log(`runner: turn ${n} for message ${m.id} failed: ${turn.reason}`);
|
||||
try {
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,44 @@
|
||||
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1185.964051ms)
|
||||
✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (344.926331ms)
|
||||
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or kept and logged (3604.06571ms)
|
||||
✔ a project clone that can't be made refuses checkout-failed, from --pm and from a session, with no run and no clone left (167.691335ms)
|
||||
✔ a runner that stops at once ends its launch with the runner's reason (286.796167ms)
|
||||
✔ a Claude Code launch with no session credential refuses before it starts (135.00899ms)
|
||||
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (238.761833ms)
|
||||
✔ a runner killed from the host ends its launch as killed, not failed (243.322808ms)
|
||||
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (107.265847ms)
|
||||
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1123.759334ms)
|
||||
✔ 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 (596.351755ms)
|
||||
✔ 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 (3564.412922ms)
|
||||
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (88.285405ms)
|
||||
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (91.417704ms)
|
||||
✔ a launch client that never closes its side doesn't hold the host's close (87.374591ms)
|
||||
✔ a runner that ignores SIGTERM is killed when the host closes (1205.194006ms)
|
||||
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (5.109326ms)
|
||||
ℹ tests 17
|
||||
ℹ suites 0
|
||||
ℹ pass 16
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 13169.402589
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/launcher.test.mjs:203:1
|
||||
✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (344.926331ms)
|
||||
AssertionError [ERR_ASSERTION]: echo: Message 5faf9ec4-06e2-49f2-b890-f52058cc9cb3 from cto, sent 2026-10-13T23:46:55.384Z, class REQUEST:
|
||||
|
||||
hello
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q109/packages/cli/tests/launcher.test.mjs:243:10)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: null,
|
||||
expected: true,
|
||||
operator: '==',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
diff --git a/packages/harness/src/runner.mjs b/packages/harness/src/runner.mjs
|
||||
index 00595542b..87d66dd36 100644
|
||||
--- a/packages/harness/src/runner.mjs
|
||||
+++ b/packages/harness/src/runner.mjs
|
||||
@@ -62,7 +62,10 @@ export function founderCheck(policy, env) {
|
||||
|
||||
// The request text one message becomes.
|
||||
export function turnRequest(m) {
|
||||
- const head = [`Message ${m.id} from ${m.from_role}`, `class ${m.class}`];
|
||||
+ const head = [`Message ${m.id} from ${m.from_role}`];
|
||||
+ // Row 109 (#1603): when the sender sent it, so a message that waited reads as old.
|
||||
+ if (m.at) head.push(`sent ${m.at}`);
|
||||
+ head.push(`class ${m.class}`);
|
||||
if (m.in_reply_to) head.push(`in reply to ${m.in_reply_to}`);
|
||||
if (m.decision) head.push(`citing decision ${m.decision}`);
|
||||
// Row 90 (#1584): the human input id task_create cites as its request.
|
||||
@@ -70,6 +73,22 @@ export function turnRequest(m) {
|
||||
return `${head.join(", ")}:\n\n${m.body}`;
|
||||
}
|
||||
|
||||
+// Row 109 (#1603): the block at the top of a turn's request. It is what the broker said at `readAt`,
|
||||
+// the truth for launch state and task status over any older message.
|
||||
+export function stateBlock(state, readAt) {
|
||||
+ const { launch, tasks } = state;
|
||||
+ const since = launch.at ? ` since ${launch.at} (event ${launch.event})` : " (never switched)";
|
||||
+ const shown = tasks.open.length < tasks.total ? `, the first ${tasks.open.length} of cap ${tasks.cap}` : "";
|
||||
+ const open = tasks.open.map((t) => `- ${t.task_ref} ${t.title ?? ""}`.trimEnd());
|
||||
+ return [
|
||||
+ `Current state, read at ${readAt}:`,
|
||||
+ `Launches: ${launch.state}${since}.`,
|
||||
+ `Open tasks: ${tasks.total}${shown}.`,
|
||||
+ ...open,
|
||||
+ "",
|
||||
+ ].join("\n");
|
||||
+}
|
||||
+
|
||||
function clip(text) {
|
||||
return text.length > BODY_MAX ? text.slice(0, BODY_MAX) + "\n[answer cut at 32000 characters]" : text;
|
||||
}
|
||||
@@ -339,7 +358,23 @@ export async function main(runDir, { stdin = process.stdin, env = process.env, l
|
||||
}
|
||||
for (const m of messages) {
|
||||
if (stopping) break loop;
|
||||
- const turn = await runTurn({ session, request: turnRequest(m), n: ++n, env: turnEnv, onChild: (c) => (child = c) });
|
||||
+ // Row 109 (#1603): read the state now, not when the message was sent. A failed read fails the turn.
|
||||
+ let block = null;
|
||||
+ let unread = null;
|
||||
+ try {
|
||||
+ block = stateBlock(await client.call("state"), new Date().toISOString());
|
||||
+ } catch (e) {
|
||||
+ if (SESSION_GONE.has(e.code)) {
|
||||
+ log(`runner: session stopped working: ${e.code}`);
|
||||
+ code = EXIT.session;
|
||||
+ break loop;
|
||||
+ }
|
||||
+ unread = `could not read the current state: ${e.code ?? e.message}`;
|
||||
+ }
|
||||
+ ++n;
|
||||
+ const turn = unread
|
||||
+ ? { ok: false, reason: unread }
|
||||
+ : await runTurn({ session, request: turnRequest(m), n, env: turnEnv, onChild: (c) => (child = c) });
|
||||
if (stopping) break loop;
|
||||
if (!turn.ok) log(`runner: turn ${n} for message ${m.id} failed: ${turn.reason}`);
|
||||
try {
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -1,40 +1,73 @@
|
||||
62bce061032623636835100f60b3ab29d2d6399603c0c89100cfec4eaf98ee28 agents/researcher/work/queue-109/build.md
|
||||
1a92b106886608a836d2b4683d5d0f4cfa7c6ea7a6c0eff0e0d2527ef60b6413 agents/researcher/work/queue-109/mutants/control-bus.out
|
||||
73b2e6bb1b8ffdd15ae9253cbd84ed93297cb58116fa99a55a34f581cb876aa5 agents/researcher/work/queue-109/build.md
|
||||
28e3f73e67ac45eb2c2e85d7aa7b876c66482eeaf14f254da37578e273dcdf01 agents/researcher/work/queue-109/mutants/control-bus.out
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa agents/researcher/work/queue-109/mutants/control-bus.rc
|
||||
23883a50bb236ccbfdeb123097f29c4db25e8322c3b68549891fa825fc5149ab agents/researcher/work/queue-109/mutants/control-runner.out
|
||||
0d170b35b5ebabc40e537964f1decd049834526157561479eeffdfd71f68ea76 agents/researcher/work/queue-109/mutants/control-cli.out
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa agents/researcher/work/queue-109/mutants/control-cli.rc
|
||||
e5f29e8b6aa4b6edd9e59c61ecde0942a5a341681af68dac0a810b9dceb22939 agents/researcher/work/queue-109/mutants/control-runner.out
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa agents/researcher/work/queue-109/mutants/control-runner.rc
|
||||
f48ac9ad7b681fbfa417604338d6cd320259e746d097b98bf7cf64c85544ac17 agents/researcher/work/queue-109/mutants/m1-drop-block.out
|
||||
de840a75741379293b74a886d34a8f1d6e61d5942eef78017adc454345551464 agents/researcher/work/queue-109/mutants/control-seat.out
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa agents/researcher/work/queue-109/mutants/control-seat.rc
|
||||
cca44a131297915176d08eea032a960bb0f49461031a600e5ee70441d936c426 agents/researcher/work/queue-109/mutants/m10-cli-drop-time.out
|
||||
f4598e1f4f1182dfdc5de727acb53f84632d81c16f0f52adff4262a4c73f5a08 agents/researcher/work/queue-109/mutants/m10-cli-drop-time.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/researcher/work/queue-109/mutants/m10-cli-drop-time.rc
|
||||
4bef64ca4515a9995eca658ba80009885d8dc1391c27e46f5fb92e56a6ddf3e3 agents/researcher/work/queue-109/mutants/m11-human-checked-as-agent.out
|
||||
0bc8d794d8bbb98962d9fb473afc6c80add615a764fc19af13e5a8dcc56d2f0d agents/researcher/work/queue-109/mutants/m11-human-checked-as-agent.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/researcher/work/queue-109/mutants/m11-human-checked-as-agent.rc
|
||||
deeaaa16a91bb8c7fd18445c770e29d329da04bab076d8d93937e98eb7447754 agents/researcher/work/queue-109/mutants/m12-state-writes-event.out
|
||||
79e9850adedf959bad88a128f45bd04d58af74cb90ccd690c38eb1a147d0ca56 agents/researcher/work/queue-109/mutants/m12-state-writes-event.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/researcher/work/queue-109/mutants/m12-state-writes-event.rc
|
||||
a996e03986c6348f7f7510da1f3f71e2fd64639d1b71225c5e7c3ec0d8c9de20 agents/researcher/work/queue-109/mutants/m1-drop-block.out
|
||||
55662e82c005a5042cb09bed61ab28d088cf424871d0ec6200bf0d56aaa3d11b agents/researcher/work/queue-109/mutants/m1-drop-block.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/researcher/work/queue-109/mutants/m1-drop-block.rc
|
||||
fea2ebac6cedf9a2018a018454ae3ac16f744b5bec3c4ee2e031d03ffc7912aa agents/researcher/work/queue-109/mutants/m2-drop-time.out
|
||||
d1a05d1001fc826904107a8cc77a8dee2562acbca1e00f16851e0d1b590c414b agents/researcher/work/queue-109/mutants/m2-drop-time.out
|
||||
f4598e1f4f1182dfdc5de727acb53f84632d81c16f0f52adff4262a4c73f5a08 agents/researcher/work/queue-109/mutants/m2-drop-time.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/researcher/work/queue-109/mutants/m2-drop-time.rc
|
||||
9a1ef422b9c1163cb95f58e481090d700bc077589f9d651d1ef822189c4f7cb6 agents/researcher/work/queue-109/mutants/m3-drop-failure-check.out
|
||||
64cab3a8c4fc464577f9006cdd745464abb76f18453cf27de6b10c8de8f68413 agents/researcher/work/queue-109/mutants/m3-drop-failure-check.out
|
||||
b30ee17aa4144cb6f4f2c64ea09a0eff00a2ba7a09f64ebb9a6c59a6ca06523a agents/researcher/work/queue-109/mutants/m3-drop-failure-check.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/researcher/work/queue-109/mutants/m3-drop-failure-check.rc
|
||||
d65461b462514360d80d7e1677b0f6dc1223041d1bb19f867f4d253e4a1ab603 agents/researcher/work/queue-109/mutants/m4-oldest-launch-event.out
|
||||
ced0a5f192f1302bffb5d73608dbc3dddae4ee0c117ad92a8513d92c37c09ce7 agents/researcher/work/queue-109/mutants/m4-oldest-launch-event.patch
|
||||
51e62f25855f95a8fe659967fe6d5b6cf66b6e0fbe12f5daa838fa739d1623ac agents/researcher/work/queue-109/mutants/m4-oldest-launch-event.out
|
||||
59a4e56ccb52bd638413c65548348dc436b4df139ce26422f5724534bcd74e07 agents/researcher/work/queue-109/mutants/m4-oldest-launch-event.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/researcher/work/queue-109/mutants/m4-oldest-launch-event.rc
|
||||
b7aeeda30f679b5bcb2d0132cdf32f90e15a092ca5cc618090b07a62e8ff3d6a agents/researcher/work/queue-109/mutants/m5-closed-in-open.out
|
||||
758218267fd42cba65d4cb39a03dbd45b0cc54dbb8191499a29f1ec3b067dcdf agents/researcher/work/queue-109/mutants/m5-closed-in-open.patch
|
||||
1d58196f6ff8b984b2862b112f7f4bf3346fdbe2676de9a1bec4ad9219f0dc72 agents/researcher/work/queue-109/mutants/m5-closed-in-open.out
|
||||
4a83008708bee76df67e9a63a9743a0262705fb89ccc50dba594d4eaeb936ab1 agents/researcher/work/queue-109/mutants/m5-closed-in-open.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/researcher/work/queue-109/mutants/m5-closed-in-open.rc
|
||||
b4b2bec5b1ca6acf5f830c6eb3aeba6a74a1964168749c04d45ad673b15d9503 agents/researcher/work/queue-109/mutants/m6-cap-unstated.out
|
||||
f047e709535a10ba427eb2a6585c7dde3c7ea16b9934a295450a6d8eea5db815 agents/researcher/work/queue-109/mutants/m6-cap-unstated.patch
|
||||
10019515788b500d0aa58de1ab6f86775a1d773212150c1aba3affe53d4777ad agents/researcher/work/queue-109/mutants/m6-cap-unstated.out
|
||||
9f69bd0648872df396cd4e34a064fb888bd43b1c11e23d7c5e7d4fc1b08f46bc agents/researcher/work/queue-109/mutants/m6-cap-unstated.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/researcher/work/queue-109/mutants/m6-cap-unstated.rc
|
||||
c3ff3575420cebbb57aa665592c17c2e7cc49da90f9800f0d49283f58c129863 agents/researcher/work/queue-109/mutants-run.txt
|
||||
34ba363fba2e5b9384a8662e2402bc1abcb6a796ad718b01571b218c2018f797 agents/researcher/work/queue-109/mutants.sh
|
||||
08f485f1d50de683bcdb97de4d77a58f325854634cbf424c10f4b60f7092ac56 agents/researcher/work/queue-109/mutants/m7-seat-drop-block.out
|
||||
55662e82c005a5042cb09bed61ab28d088cf424871d0ec6200bf0d56aaa3d11b agents/researcher/work/queue-109/mutants/m7-seat-drop-block.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/researcher/work/queue-109/mutants/m7-seat-drop-block.rc
|
||||
3edcc176125507990d48902c48899e6d35afad2a534276ce0b54fc3880345923 agents/researcher/work/queue-109/mutants/m8-seat-drop-time.out
|
||||
f4598e1f4f1182dfdc5de727acb53f84632d81c16f0f52adff4262a4c73f5a08 agents/researcher/work/queue-109/mutants/m8-seat-drop-time.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/researcher/work/queue-109/mutants/m8-seat-drop-time.rc
|
||||
ba7cbdbd3f94c938e293b15e2295b6a37a04fb579bcb7727e890f0a8d38ce332 agents/researcher/work/queue-109/mutants/m9-cli-drop-block.out
|
||||
55662e82c005a5042cb09bed61ab28d088cf424871d0ec6200bf0d56aaa3d11b agents/researcher/work/queue-109/mutants/m9-cli-drop-block.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/researcher/work/queue-109/mutants/m9-cli-drop-block.rc
|
||||
191723cc2f32e404887f5aa35d196146aed6c00b04b5c7837008a76484218505 agents/researcher/work/queue-109/mutants-run.txt
|
||||
43a7e3acea410fb64461783df37765926419bd1e4e42b73d7f09120ca530a8a3 agents/researcher/work/queue-109/mutants.sh
|
||||
b0ef977027ad0a39b69347dc2d3f20765d4d8e1d57d783b03c1cc653b96e5fdb agents/researcher/work/queue-109/pm-workspace-names.txt
|
||||
55fcdf13135818abde7157f78f4f9afbfcedc292d95fe02109908392f69fc4c7 agents/researcher/work/queue-109/row109.patch
|
||||
bc33dae0ca339fb80f8ed5a0de7d84ea73df1f8f684f5001eed10dcb1abad9ec agents/researcher/work/queue-109/suites/bus-node-suite.out
|
||||
d5731e9121956e7629a2e8040b4ee329f74996a680707ed3a9ae8193568df775 agents/researcher/work/queue-109/suites/harness-node-suite.out
|
||||
b996f93d6f321014cd4e9ef327b6004bf8c525accf4e7c74cf3ca6ff3b145dd7 agents/researcher/work/queue-109/suites/queue-node-suite.out
|
||||
c7ed4247eb9916e67bf5a2dd86687110fbf3e0eaaaa234b31b551d26497c5856 agents/researcher/work/queue-109/suites.sh
|
||||
4d05f6f428e205123389507ff5e43fd57165819dd3e7d982a4ec312fdd74a6ac agents/researcher/work/queue-109/row109.patch
|
||||
cc4327127c15c7b99143a1f111161ddc2463fcb5387a3ae92fdb1eb6c6abac41 agents/researcher/work/queue-109/suites/business-node-suite.out
|
||||
c99a8cf74ecb82a5e8a530c66f06fb201037968e0788111585c8016faecb9be3 agents/researcher/work/queue-109/suites/bus-node-suite.out
|
||||
97ad13abeadd51824384267dd1bea141c8ad985ddc75e859a42ad0feade9c289 agents/researcher/work/queue-109/suites/cli-node-suite.out
|
||||
427998cdb93428264415331da05721e645278f86e3a08949039eb6b4bcf139ed agents/researcher/work/queue-109/suites/control-board-node-suite.out
|
||||
0fae947f89eea822a0a3f326e52de5ae865dda16a2878ec581357d60788083c2 agents/researcher/work/queue-109/suites/conversation-node-suite.out
|
||||
6ea2b0436db848545329867cda7c8e1cd27a597b4a3dd6559cf744802c2cf961 agents/researcher/work/queue-109/suites/discord-node-suite.out
|
||||
b4d7f4aec64e64d4b4d1003466cf748ba08c7c4d4b90998b8e82b791c3cd9e22 agents/researcher/work/queue-109/suites/harness-node-suite.out
|
||||
b781cd90ea4148fae4c424d6d24dc9c4fe2a9fa976a97a3e9958edb8effde0c6 agents/researcher/work/queue-109/suites/ledger-node-suite.out
|
||||
f748e3c5c234107e244aaf7df4b41a744fb2645bb9e00fa75c3b543f9635ebdb agents/researcher/work/queue-109/suites/mosaic-node-suite.out
|
||||
dc8d4471e214725f143deb5ab4be70cd61b4783a8ffdadfff6616d58e98b6663 agents/researcher/work/queue-109/suites/queue-node-suite.out
|
||||
648b5db561d203df78ddea8402eff16cba01c86a1b49646c7f4778b6edcced24 agents/researcher/work/queue-109/suites/runs-node-suite.out
|
||||
d2a29b07ee60c9e9280d9578feffdaf88a0b39da919e24b56aa423899a728b24 agents/researcher/work/queue-109/suites/seat-node-suite.out
|
||||
9879c3a3dc5b57a4880a8e6adb5bd8a043a724d722af9f6f30ed4f1c6a5e1472 agents/researcher/work/queue-109/suites.sh
|
||||
bf32777e2ec3adfbb44b13e88373c73da1368b2704054ca68f81dbf60149b40b agents/researcher/work/queue-109/suites/tasks-node-suite.out
|
||||
f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 agents/researcher/work/queue-109/suites/test-auth.out
|
||||
e1206366f07f3d9029530dcdcb55c0282d3f052adc60fc7fd51642d5d4ec3600 agents/researcher/work/queue-109/suites/test-conductor.out
|
||||
23fc908f9f474ee5c8e2dcc8032a7955eb99633962c0482be890948f49e4bd25 agents/researcher/work/queue-109/suites/test-conductor.out
|
||||
52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 agents/researcher/work/queue-109/suites/test-config.out
|
||||
7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe agents/researcher/work/queue-109/suites/test-discord.out
|
||||
bd12416272ae0a9d9d78e3a340663c68a8c7423a2c7ed0ad2c2558ec97d8be90 agents/researcher/work/queue-109/suites/test-extension-package.out
|
||||
483aa363cc35f21fe85b3de98c9e585787e3cce450ca67cf0a88097ff497eae6 agents/researcher/work/queue-109/suites/test-extension-package.out
|
||||
83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f agents/researcher/work/queue-109/suites/test-foundation.out
|
||||
4636814ca4819fa843fe9abf6285bb6e32a295997221252269cc4bf112f1aabf agents/researcher/work/queue-109/suites/test-queue.out
|
||||
fa832794ee35025fb4559d58d01a270f6bff2eb65e863e9075b797f47fa68545 agents/researcher/work/queue-109/suites/test-release.out
|
||||
ccea37f4ffde1f160dcd1a1864988083a645d974e892065c956b338d0f8c853a agents/researcher/work/queue-109/suites/test-task.out
|
||||
f37b5348a06b156cf61d3102cd9c15807c18b08e591c559dc825794200b47afe agents/researcher/work/queue-109/suites/webui-node-suite.out
|
||||
|
||||
@@ -11,7 +11,7 @@ index 0cbbc8192..b0c469263 100644
|
||||
|
||||
### Agent state (row 59, #1548)
|
||||
diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs
|
||||
index 732c7310e..7565e82da 100644
|
||||
index 732c7310e..ddc18f21c 100644
|
||||
--- a/packages/bus/src/broker.mjs
|
||||
+++ b/packages/bus/src/broker.mjs
|
||||
@@ -654,7 +654,7 @@ export class Broker {
|
||||
@@ -30,7 +30,7 @@ index 732c7310e..7565e82da 100644
|
||||
+ // Row 109 (#1603): what a run needs to know before it plans. The business's launch state with
|
||||
+ // the time and id of the event that set it (`allowed`, no time and no id when none has), and
|
||||
+ // the open tasks as the tracker snapshot shows them: refs and titles, at most `cap` of them,
|
||||
+ // `total` counting every open task. A read for role and reader sessions; nothing is stored.
|
||||
+ // `total` counting every open task. A read for role, reader and human sessions; nothing is stored.
|
||||
+ case 'state': {
|
||||
+ keys(a, []);
|
||||
+ if (!s.human && !s.reader) this.#agent(s);
|
||||
@@ -49,10 +49,10 @@ index 732c7310e..7565e82da 100644
|
||||
case 'tasks':
|
||||
diff --git a/packages/bus/tests/state.test.mjs b/packages/bus/tests/state.test.mjs
|
||||
new file mode 100644
|
||||
index 000000000..3a14c7ae9
|
||||
index 000000000..7ae0085a7
|
||||
--- /dev/null
|
||||
+++ b/packages/bus/tests/state.test.mjs
|
||||
@@ -0,0 +1,89 @@
|
||||
@@ -0,0 +1,113 @@
|
||||
+import test from 'node:test';
|
||||
+import assert from 'node:assert/strict';
|
||||
+import { mkdtempSync, rmSync } from 'node:fs';
|
||||
@@ -142,6 +142,50 @@ index 000000000..3a14c7ae9
|
||||
+ assert.throws(() => state(pm, { business: 'other' }), { code: 'invalid-request' });
|
||||
+ assert.throws(() => state('f'.repeat(64)), /unauthenticated/);
|
||||
+});
|
||||
+
|
||||
+test('a human session reads the same state, with the same cap', (t) => {
|
||||
+ const { snap, record, human, pm, state } = setup(t);
|
||||
+ record(...Array.from({ length: TASK_PAGE + 2 }, (_, k) => snap(k + 1)));
|
||||
+ const read = state(human);
|
||||
+ assert.deepEqual(read, state(pm));
|
||||
+ assert.equal(read.tasks.cap, TASK_PAGE);
|
||||
+ assert.equal(read.tasks.open.length, TASK_PAGE);
|
||||
+ assert.equal(read.tasks.total, TASK_PAGE + 2);
|
||||
+});
|
||||
+
|
||||
+test('reading state writes nothing: every table keeps its row count', (t) => {
|
||||
+ const { b, store, human, pm, reader, snap, record, state } = setup(t);
|
||||
+ b.request(human, { verb: 'launch.revoke' });
|
||||
+ record(snap(1), snap(2));
|
||||
+ const counts = () => Object.fromEntries(
|
||||
+ store.all("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
|
||||
+ .map(({ name }) => [name, store.get(`SELECT COUNT(*) AS n FROM "${name}"`).n]),
|
||||
+ );
|
||||
+ const before = counts();
|
||||
+ assert.ok(before.events > 0 && before.task_snapshots > 0);
|
||||
+ for (const cap of [pm, reader, human]) state(cap);
|
||||
+ assert.deepEqual(counts(), before);
|
||||
+});
|
||||
diff --git a/packages/cli/tests/launcher.test.mjs b/packages/cli/tests/launcher.test.mjs
|
||||
index 7e2623728..80d945dc2 100644
|
||||
--- a/packages/cli/tests/launcher.test.mjs
|
||||
+++ b/packages/cli/tests/launcher.test.mjs
|
||||
@@ -234,10 +234,14 @@ test("the PM launches a coder through its launch tool; the coder answers; refusa
|
||||
}
|
||||
|
||||
// The coder claimed its role and answers its own messages.
|
||||
+ const before = Date.now();
|
||||
const hello = await client.call("message.send", { to: "coder", class: "REQUEST", body: "hello" });
|
||||
const echo = await until(async () => (await client.call("message.receive")).find((m) => m.in_reply_to === hello.id));
|
||||
assert.equal(echo.from_role, "coder");
|
||||
- assert.match(echo.body, /^echo: Message .* from cto, class REQUEST:\n\nhello/);
|
||||
+ // Row 109 (#1603): the turn opens with the state block, then the header with the time it was sent.
|
||||
+ const turn = echo.body.match(new RegExp(`^echo: Current state, read at \\S+:\nLaunches: allowed \\(never switched\\)\\.\nOpen tasks: 0\\.\n\nMessage ${hello.id} from cto, sent (\\S+), class REQUEST:\n\nhello$`));
|
||||
+ assert.ok(turn, echo.body);
|
||||
+ assert.ok(Date.parse(turn[1]) >= before - 1000 && Date.parse(turn[1]) <= Date.now(), "the sent time is when it was sent");
|
||||
|
||||
// Host refusals, asked with the cto capability (steps 2 to 8 come before
|
||||
// the broker's role.launch check, which cto would fail).
|
||||
diff --git a/packages/harness/README.md b/packages/harness/README.md
|
||||
index 6f87ac731..4d2d6fb96 100644
|
||||
--- a/packages/harness/README.md
|
||||
@@ -388,6 +432,35 @@ index 777d16148..12b683cb7 100644
|
||||
test("a SIGTERM before the claim stops the runner with exit 0 and no claim", async (t) => {
|
||||
const ctx = await setup(t);
|
||||
// session.json becomes a FIFO: main opens it right after its handlers go
|
||||
diff --git a/packages/seat/tests/sandbox.test.mjs b/packages/seat/tests/sandbox.test.mjs
|
||||
index c2bb68347..9995b6b03 100644
|
||||
--- a/packages/seat/tests/sandbox.test.mjs
|
||||
+++ b/packages/seat/tests/sandbox.test.mjs
|
||||
@@ -214,7 +214,8 @@ async function session(t, { expose = () => [] } = {}) {
|
||||
writeFileSync(join(bundle, "tools.json"), "[]");
|
||||
const adapter = join(root, "adapter.sh");
|
||||
writeFileSync(adapter, [
|
||||
- `body=$(printf '%s\\n' "$MOSAIC_REQUEST" | sed '1,/^$/d')`,
|
||||
+ // Row 109 (#1603): a state block opens the request, so the body is what follows the "Message ...:" header and its blank line.
|
||||
+ `body=$(printf '%s\\n' "$MOSAIC_REQUEST" | sed '1,/^Message .*:$/d' | sed '1d')`,
|
||||
`case $body in TOOL\\ *) exec '${process.execPath}' '${FAKE}' ;; esac`,
|
||||
`out=$(cd "$MOSAIC_WORKSPACE" && /bin/sh -c "$body" 2>&1); rc=$?`,
|
||||
`printf 'rc=%s\\n%s' "$rc" "$out"`,
|
||||
diff --git a/packages/seat/tests/session.test.mjs b/packages/seat/tests/session.test.mjs
|
||||
index 72e8c9453..e072709cb 100644
|
||||
--- a/packages/seat/tests/session.test.mjs
|
||||
+++ b/packages/seat/tests/session.test.mjs
|
||||
@@ -224,7 +224,9 @@ test("a session runs in its sandbox as pid 1 of its namespace, claims, answers,
|
||||
if (!reply) await new Promise((r) => setTimeout(r, 50));
|
||||
}
|
||||
assert.ok(reply, "the session answered");
|
||||
- assert.match(reply.body, /^echo: Message .* from pm, class REQUEST:\n\nhello/);
|
||||
+ // Row 109 (#1603): the turn opens with the state block, then the header with the time it was sent.
|
||||
+ const sentAt = store.get("SELECT at FROM messages WHERE id=?", sent.id).at;
|
||||
+ assert.match(reply.body, new RegExp(`^echo: Current state, read at \\S+:\nLaunches: allowed \\(never switched\\)\\.\nOpen tasks: 0\\.\n\nMessage ${sent.id} from pm, sent ${sentAt}, class REQUEST:\n\nhello$`));
|
||||
|
||||
const registry = new Registry(dataRoot);
|
||||
registry.add({ business: "acme", instance: "coder", run, pid: child.pid, startTime: startTimeOf(child.pid), runnerPid: runner.pid, runnerStartTime: runner.startTime });
|
||||
diff --git a/roles/pm.md b/roles/pm.md
|
||||
index 522ac9c0e..8ef433f58 100644
|
||||
--- a/roles/pm.md
|
||||
|
||||
@@ -12,6 +12,10 @@ script() { # name
|
||||
setsid -w timeout -k 10 1200 bash "scripts/$1.sh" 2>&1 | tee "$OUT/$1.out" >/dev/null
|
||||
echo "$1: rc=${PIPESTATUS[0]} $(grep -iE 'passed|failed' "$OUT/$1.out" | tail -1)"
|
||||
}
|
||||
for d in bus harness; do node_suite $d packages/$d; done
|
||||
# Round 3: every package node suite, not only bus and harness.
|
||||
for d in bus business cli control-board conversation discord harness ledger mosaic queue runs seat tasks webui; do
|
||||
ls "packages/$d"/tests/*.test.mjs >/dev/null 2>&1 || { echo "$d: no tests/*.test.mjs"; continue; }
|
||||
node_suite $d packages/$d
|
||||
done
|
||||
for s in test-auth test-conductor test-config test-discord test-extension-package test-foundation test-queue test-release test-task; do script $s; done
|
||||
echo done
|
||||
|
||||
@@ -1,142 +1,144 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (28.297351ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (26.151014ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (50.067427ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (22.161722ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (31.653342ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (33.064525ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (36.106172ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (30.478949ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (123.330395ms)
|
||||
✔ agents: stranded is per role and per business (35.147374ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (14.397001ms)
|
||||
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (29.665485ms)
|
||||
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (32.302635ms)
|
||||
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (36.864032ms)
|
||||
✔ launch events require a human CLI capability; generic emit cannot forge authority events (22.624026ms)
|
||||
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (26.418409ms)
|
||||
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (25.730416ms)
|
||||
✔ task action subjects and linked decision trail are complete and ordered (42.04217ms)
|
||||
✔ launch binding is durable and reconnecting requires the identical trusted record (19.549861ms)
|
||||
✔ business isolation includes inherited object names and cross-business message references (30.891376ms)
|
||||
✔ authority never transfers between action, run, target, unresolved or replaced role holder (36.279429ms)
|
||||
✔ task projection uses schema current view, skipping earlier and equal-start polls (23.916643ms)
|
||||
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (33.145318ms)
|
||||
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (17.342213ms)
|
||||
✔ both arbiters require human resolution when their cross-role route is themselves (26.232801ms)
|
||||
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (20.62886ms)
|
||||
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.778225ms)
|
||||
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (23.765412ms)
|
||||
✔ credentials refuses a role session and any argument (20.404226ms)
|
||||
✔ credentials reads the status on each request, and a broker without one lists none (24.326082ms)
|
||||
✔ the broker process answers credentials without a token, path or variable in its output, log or store (93.242027ms)
|
||||
✔ only validated broker references load; returned data and exceptions cannot expose a known token (7.033452ms)
|
||||
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (3.013949ms)
|
||||
✔ expiry refuses use and env references never become client data (1.544583ms)
|
||||
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (2.277426ms)
|
||||
✔ opaque tokens shorter than 16 characters refuse before use (0.764888ms)
|
||||
✔ roles lists open decisions routed to a role, and never the human inbox (39.554268ms)
|
||||
✔ recent lists closed decisions of every route, newest close first, with how each closed (37.199033ms)
|
||||
✔ recent gives the closing event as closed when the decision was seen first (40.823498ms)
|
||||
✔ recent stops at RECENT rows (146.209586ms)
|
||||
✔ every row is what trail already gives the same session for that id (50.272751ms)
|
||||
✔ a role session can't list decisions, so it can't read another role's open decisions (53.874347ms)
|
||||
✔ decisions is a read: one business, strict arguments, nothing written (44.63042ms)
|
||||
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (24.633583ms)
|
||||
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (15.749064ms)
|
||||
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (16.279704ms)
|
||||
✔ endLaunch leaves a claim another run took alone (23.202804ms)
|
||||
✔ refuse records action.refused against the caller with the code only (18.116231ms)
|
||||
✔ launches off refuses role.launch with launch-revoked until launches on (21.321294ms)
|
||||
✔ broker process: launch ops authorize role.launch, record refusals and end runs (133.544824ms)
|
||||
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.828563ms)
|
||||
✔ process reader gets own kernel identity without exposing environment values (0.926592ms)
|
||||
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.124436ms)
|
||||
✔ real pid 1 remains inspectable when its environment is protected (0.65044ms)
|
||||
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (32.979603ms)
|
||||
✔ missing and foreign-business citations refuse and roll back message and grant (20.474351ms)
|
||||
✔ cross-role sends still need a matching resolved decision and consume it once (34.541326ms)
|
||||
✔ messages pages all human messages newest first without writes; read state is history (444.138897ms)
|
||||
✔ messages refuses role callers, invalid paging and cursors outside this human feed (20.427029ms)
|
||||
✔ views messages passes only its paging arguments through (0.347001ms)
|
||||
✔ messages isolates businesses even with a valid foreign cursor (13.183145ms)
|
||||
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (102.394931ms)
|
||||
✔ startup token refusal returns safe code without value or partial listening broker (63.271273ms)
|
||||
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (98.670383ms)
|
||||
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (137.631589ms)
|
||||
✔ trusted host registers later launches; socket clients never have a registration verb (74.175173ms)
|
||||
✔ runtime excludes declared project roots even when host supplies no repoRoots (41.416891ms)
|
||||
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (60.256132ms)
|
||||
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (49.259344ms)
|
||||
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (25.624504ms)
|
||||
✔ a message with no reply gives an empty list (20.016322ms)
|
||||
✔ replies come oldest first and stop at REPLIES (120.444271ms)
|
||||
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (44.587049ms)
|
||||
✔ strict arguments; a role session gets human-required (44.089256ms)
|
||||
✔ views passes replies through as one call with the id (2.541981ms)
|
||||
✔ requirements returns the committed list to a holding role session, and no one else (32.077319ms)
|
||||
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (22.074738ms)
|
||||
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (20.246744ms)
|
||||
✔ a push is recorded as the action.allowed for git.push.working on the task, and only the host records one (36.151608ms)
|
||||
✔ review.request names the latest pushed commit and sends the reviewer a REVIEW-REQUEST (30.682906ms)
|
||||
✔ review.request says which run holds the reviewer role, or null, and a null request waits for a claim (40.310639ms)
|
||||
✔ review.request refuses a commit that isn't the latest push, a non-reviewer and a role without the action (57.238224ms)
|
||||
✔ reviewFetch answers only the addressed reviewer, for the request it cites (41.290709ms)
|
||||
✔ review.verdict answers one request once, to the role that asked (46.002266ms)
|
||||
✔ task.close on a pushed task needs an approve verdict for the latest push (55.690651ms)
|
||||
✔ a refusal the host decided is filed on the task it names, and only a task ref counts (26.625638ms)
|
||||
✔ v3b prototype refusals, views and append-only mutations (107.680716ms)
|
||||
✔ gated approval authorizes once, survives store reopen, and fresh approval works (41.650704ms)
|
||||
✔ another run cannot consume an approval; a failed check leaves it usable (32.015671ms)
|
||||
✔ two scheduled callers have exactly one grant and one consumed refusal (26.932983ms)
|
||||
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (51.60255ms)
|
||||
✔ class drift gated to cross-role refuses before consumption (36.98624ms)
|
||||
✔ class drift cross-role to gated refuses before consumption (31.240912ms)
|
||||
✔ class drift gated to within-role refuses before consumption (34.31432ms)
|
||||
✔ class drift cross-role to within-role refuses before consumption (28.552349ms)
|
||||
✔ class drift within-role to gated refuses before consumption (24.790443ms)
|
||||
✔ class drift within-role to cross-role refuses before consumption (20.366658ms)
|
||||
✔ message.send consumes approval and prevents a later send or authorize (23.511105ms)
|
||||
✔ role.revoke consumes approval and prevents a later revoke or authorize (22.585085ms)
|
||||
✔ a business that never switched launches reads allowed with no time and no event (21.965042ms)
|
||||
✔ launches revoked and then restored read allowed, with the restore time and event id (21.91704ms)
|
||||
✔ a task closed on the tracker leaves the open list, and total counts the open ones (21.469025ms)
|
||||
✔ a tombstone is not open (22.705282ms)
|
||||
✔ the open list stops at the cap and says so; total still counts every open task (30.531354ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (22.61769ms)
|
||||
✔ creates private WAL store and excludes a second writer until explicit close (21.22909ms)
|
||||
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (23.375799ms)
|
||||
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (24.087794ms)
|
||||
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (80.380382ms)
|
||||
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (16.980031ms)
|
||||
✔ async transactions refuse before invoking their function (12.165568ms)
|
||||
✔ the session default fits under the reply cap and pages to the end with next (210.151753ms)
|
||||
✔ the short form: no description, assignees by role, every listed key (101.057075ms)
|
||||
✔ each filter, alone and together, and total counts every match (170.585166ms)
|
||||
✔ task_ref reads one task in full, as the whole board has it (95.139805ms)
|
||||
✔ no arguments is the read the Console and mosaic make, unchanged (78.859592ms)
|
||||
✔ an unknown argument or a bad value refuses invalid-request (327.211074ms)
|
||||
✔ tasks rows carry priority and requirement for every session that reads tasks (62.480313ms)
|
||||
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (62.965212ms)
|
||||
✔ an empty business and one with snapshots but no task.created read cleanly (16.835199ms)
|
||||
✔ recordTask keeps sync reads and a role write apart (33.465124ms)
|
||||
✔ read_at must be one canonical UTC format, so the projection compares strings safely (12.037918ms)
|
||||
✔ a bad entry refuses the whole record (12.462407ms)
|
||||
✔ taskView reads the projection for one business (15.861849ms)
|
||||
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (35.14223ms)
|
||||
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (281.596722ms)
|
||||
✔ without an adapter the server refuses every task verb (17.498175ms)
|
||||
✔ the runtime refuses an invalid adapter and closes a valid one (18.80034ms)
|
||||
✔ the process loads the S3 adapter from plain-data trackers (114.455343ms)
|
||||
✔ socket capability stamps launch identity; shared views use wire, no SQL client (42.344319ms)
|
||||
✔ two wire claims serialize; a lost reply never automatically retries (28.837779ms)
|
||||
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (20.272911ms)
|
||||
✔ client preserves UTF-8 when a response divides a multibyte character (14.60498ms)
|
||||
✔ committed mutation followed by dropped reply reports unknown and is never retried (17.674993ms)
|
||||
ℹ tests 134
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (53.345751ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (37.049209ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (54.221021ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (25.074686ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (29.201775ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (30.503874ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (30.483115ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (33.991234ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (138.836602ms)
|
||||
✔ agents: stranded is per role and per business (41.733601ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (15.546004ms)
|
||||
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (44.720949ms)
|
||||
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (40.518887ms)
|
||||
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (36.206591ms)
|
||||
✔ launch events require a human CLI capability; generic emit cannot forge authority events (23.85051ms)
|
||||
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (31.166172ms)
|
||||
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (27.887162ms)
|
||||
✔ task action subjects and linked decision trail are complete and ordered (39.656662ms)
|
||||
✔ launch binding is durable and reconnecting requires the identical trusted record (18.177199ms)
|
||||
✔ business isolation includes inherited object names and cross-business message references (37.100703ms)
|
||||
✔ authority never transfers between action, run, target, unresolved or replaced role holder (37.455402ms)
|
||||
✔ task projection uses schema current view, skipping earlier and equal-start polls (21.85863ms)
|
||||
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (26.853766ms)
|
||||
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (18.644889ms)
|
||||
✔ both arbiters require human resolution when their cross-role route is themselves (24.267217ms)
|
||||
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (24.189015ms)
|
||||
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.685061ms)
|
||||
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (27.087106ms)
|
||||
✔ credentials refuses a role session and any argument (25.35644ms)
|
||||
✔ credentials reads the status on each request, and a broker without one lists none (35.093486ms)
|
||||
✔ the broker process answers credentials without a token, path or variable in its output, log or store (116.89915ms)
|
||||
✔ only validated broker references load; returned data and exceptions cannot expose a known token (12.70728ms)
|
||||
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (3.560369ms)
|
||||
✔ expiry refuses use and env references never become client data (1.508323ms)
|
||||
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (2.567023ms)
|
||||
✔ opaque tokens shorter than 16 characters refuse before use (0.984255ms)
|
||||
✔ roles lists open decisions routed to a role, and never the human inbox (44.871314ms)
|
||||
✔ recent lists closed decisions of every route, newest close first, with how each closed (48.127659ms)
|
||||
✔ recent gives the closing event as closed when the decision was seen first (40.736679ms)
|
||||
✔ recent stops at RECENT rows (151.660483ms)
|
||||
✔ every row is what trail already gives the same session for that id (64.10788ms)
|
||||
✔ a role session can't list decisions, so it can't read another role's open decisions (47.365421ms)
|
||||
✔ decisions is a read: one business, strict arguments, nothing written (40.924426ms)
|
||||
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (34.106898ms)
|
||||
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (20.851428ms)
|
||||
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (27.220638ms)
|
||||
✔ endLaunch leaves a claim another run took alone (22.110897ms)
|
||||
✔ refuse records action.refused against the caller with the code only (19.510601ms)
|
||||
✔ launches off refuses role.launch with launch-revoked until launches on (26.457423ms)
|
||||
✔ broker process: launch ops authorize role.launch, record refusals and end runs (139.53102ms)
|
||||
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (4.626606ms)
|
||||
✔ process reader gets own kernel identity without exposing environment values (1.015438ms)
|
||||
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.13444ms)
|
||||
✔ real pid 1 remains inspectable when its environment is protected (0.458999ms)
|
||||
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (43.553394ms)
|
||||
✔ missing and foreign-business citations refuse and roll back message and grant (31.112824ms)
|
||||
✔ cross-role sends still need a matching resolved decision and consume it once (33.309267ms)
|
||||
✔ messages pages all human messages newest first without writes; read state is history (469.728142ms)
|
||||
✔ messages refuses role callers, invalid paging and cursors outside this human feed (21.468385ms)
|
||||
✔ views messages passes only its paging arguments through (0.304665ms)
|
||||
✔ messages isolates businesses even with a valid foreign cursor (15.94981ms)
|
||||
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (132.078296ms)
|
||||
✔ startup token refusal returns safe code without value or partial listening broker (73.13813ms)
|
||||
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (115.048838ms)
|
||||
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (132.173626ms)
|
||||
✔ trusted host registers later launches; socket clients never have a registration verb (73.972433ms)
|
||||
✔ runtime excludes declared project roots even when host supplies no repoRoots (46.355452ms)
|
||||
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (69.824002ms)
|
||||
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (56.460005ms)
|
||||
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (28.009884ms)
|
||||
✔ a message with no reply gives an empty list (25.087219ms)
|
||||
✔ replies come oldest first and stop at REPLIES (121.453985ms)
|
||||
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (57.07998ms)
|
||||
✔ strict arguments; a role session gets human-required (49.312075ms)
|
||||
✔ views passes replies through as one call with the id (0.343278ms)
|
||||
✔ requirements returns the committed list to a holding role session, and no one else (44.09571ms)
|
||||
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (25.102261ms)
|
||||
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (25.47151ms)
|
||||
✔ a push is recorded as the action.allowed for git.push.working on the task, and only the host records one (48.632777ms)
|
||||
✔ review.request names the latest pushed commit and sends the reviewer a REVIEW-REQUEST (37.53467ms)
|
||||
✔ review.request says which run holds the reviewer role, or null, and a null request waits for a claim (45.158269ms)
|
||||
✔ review.request refuses a commit that isn't the latest push, a non-reviewer and a role without the action (61.731826ms)
|
||||
✔ reviewFetch answers only the addressed reviewer, for the request it cites (41.268235ms)
|
||||
✔ review.verdict answers one request once, to the role that asked (46.777661ms)
|
||||
✔ task.close on a pushed task needs an approve verdict for the latest push (51.525849ms)
|
||||
✔ a refusal the host decided is filed on the task it names, and only a task ref counts (30.103337ms)
|
||||
✔ v3b prototype refusals, views and append-only mutations (120.825112ms)
|
||||
✔ gated approval authorizes once, survives store reopen, and fresh approval works (46.226675ms)
|
||||
✔ another run cannot consume an approval; a failed check leaves it usable (31.65991ms)
|
||||
✔ two scheduled callers have exactly one grant and one consumed refusal (24.587566ms)
|
||||
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (54.454972ms)
|
||||
✔ class drift gated to cross-role refuses before consumption (42.783433ms)
|
||||
✔ class drift cross-role to gated refuses before consumption (39.675355ms)
|
||||
✔ class drift gated to within-role refuses before consumption (32.743453ms)
|
||||
✔ class drift cross-role to within-role refuses before consumption (32.242986ms)
|
||||
✔ class drift within-role to gated refuses before consumption (27.697108ms)
|
||||
✔ class drift within-role to cross-role refuses before consumption (21.440093ms)
|
||||
✔ message.send consumes approval and prevents a later send or authorize (22.298102ms)
|
||||
✔ role.revoke consumes approval and prevents a later revoke or authorize (21.187526ms)
|
||||
✔ a business that never switched launches reads allowed with no time and no event (23.872134ms)
|
||||
✔ launches revoked and then restored read allowed, with the restore time and event id (22.203524ms)
|
||||
✔ a task closed on the tracker leaves the open list, and total counts the open ones (20.88584ms)
|
||||
✔ a tombstone is not open (23.622734ms)
|
||||
✔ the open list stops at the cap and says so; total still counts every open task (31.38746ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (23.147345ms)
|
||||
✔ a human session reads the same state, with the same cap (30.688306ms)
|
||||
✔ reading state writes nothing: every table keeps its row count (25.620195ms)
|
||||
✔ creates private WAL store and excludes a second writer until explicit close (21.733357ms)
|
||||
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (22.952595ms)
|
||||
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (23.813833ms)
|
||||
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (81.715645ms)
|
||||
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (19.303146ms)
|
||||
✔ async transactions refuse before invoking their function (16.729944ms)
|
||||
✔ the session default fits under the reply cap and pages to the end with next (233.154729ms)
|
||||
✔ the short form: no description, assignees by role, every listed key (100.835596ms)
|
||||
✔ each filter, alone and together, and total counts every match (181.195033ms)
|
||||
✔ task_ref reads one task in full, as the whole board has it (124.71456ms)
|
||||
✔ no arguments is the read the Console and mosaic make, unchanged (86.09897ms)
|
||||
✔ an unknown argument or a bad value refuses invalid-request (317.443818ms)
|
||||
✔ tasks rows carry priority and requirement for every session that reads tasks (55.846229ms)
|
||||
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (77.938794ms)
|
||||
✔ an empty business and one with snapshots but no task.created read cleanly (15.590214ms)
|
||||
✔ recordTask keeps sync reads and a role write apart (43.252739ms)
|
||||
✔ read_at must be one canonical UTC format, so the projection compares strings safely (14.008254ms)
|
||||
✔ a bad entry refuses the whole record (14.985013ms)
|
||||
✔ taskView reads the projection for one business (18.285037ms)
|
||||
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (30.458742ms)
|
||||
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (283.280283ms)
|
||||
✔ without an adapter the server refuses every task verb (18.820463ms)
|
||||
✔ the runtime refuses an invalid adapter and closes a valid one (21.747589ms)
|
||||
✔ the process loads the S3 adapter from plain-data trackers (141.413317ms)
|
||||
✔ socket capability stamps launch identity; shared views use wire, no SQL client (44.920877ms)
|
||||
✔ two wire claims serialize; a lost reply never automatically retries (27.892274ms)
|
||||
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (19.165955ms)
|
||||
✔ client preserves UTF-8 when a response divides a multibyte character (13.45429ms)
|
||||
✔ committed mutation followed by dropped reply reports unknown and is never retried (19.278096ms)
|
||||
ℹ tests 136
|
||||
ℹ suites 0
|
||||
ℹ pass 134
|
||||
ℹ pass 136
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1259.645185
|
||||
ℹ duration_ms 1371.754234
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
✔ config directory and file path follow MOSAIC_CONFIG (1.485384ms)
|
||||
✔ the fixture business validates and comes back frozen (5.007918ms)
|
||||
✔ two instances may share a definition (2.093996ms)
|
||||
✔ top-level refusals (5.945016ms)
|
||||
✔ arbiters and projects (6.26355ms)
|
||||
✔ role instances (3.614189ms)
|
||||
✔ Vikunja bots (7.885241ms)
|
||||
✔ a role without Vikunja takes no tracker block (2.700606ms)
|
||||
✔ credential references match the definition's services (3.156605ms)
|
||||
✔ launch (8.496656ms)
|
||||
✔ loadBusiness: file checks (2.028547ms)
|
||||
✔ loadBusiness: not a regular file (44.70848ms)
|
||||
✔ loading writes nothing (1.256643ms)
|
||||
✔ names that are Object.prototype properties don't count as declared (2.794582ms)
|
||||
✔ the shipped example refuses as written and validates once filled in (0.554612ms)
|
||||
✔ the role file loads and resolves through loadBusiness (5.625942ms)
|
||||
✔ the shipped file is exactly decision 104's list (0.953527ms)
|
||||
✔ the resolved record matches decision 104, so a widened file fails (2.082984ms)
|
||||
✔ message.send is within role and every other action is gated (1.929947ms)
|
||||
✔ a business grant of a gated action through limits does not widen the role (1.766546ms)
|
||||
✔ a launch block whose by is the chief-of-staff instance is refused (2.050183ms)
|
||||
✔ the instance takes no credential references and no tracker (3.600704ms)
|
||||
✔ usage errors exit 4 (303.711525ms)
|
||||
✔ validate: a good business exits 0 and prints instance digests (67.251231ms)
|
||||
✔ validate: project files (345.698277ms)
|
||||
✔ validate: missing files and a broken system config (285.835252ms)
|
||||
✔ validate: credential reference problems exit 2 and name each one (82.743793ms)
|
||||
✔ validate: a token file inside the repository is refused (72.909401ms)
|
||||
✔ validate: role definitions come from MOSAIC_ROLES_DIR (235.011709ms)
|
||||
✔ resolve: prints one instance's record (237.87353ms)
|
||||
✔ resolve: refusals (393.644916ms)
|
||||
✔ parse: exactly one of file or env, plus the service's date (3.009534ms)
|
||||
✔ check: a good file has no problems (0.82114ms)
|
||||
✔ check never opens the file: a write-only token passes (0.452289ms)
|
||||
✔ check: file problems (1.096133ms)
|
||||
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (1.042272ms)
|
||||
✔ check: dates and environment references (0.515004ms)
|
||||
✔ path and load (2.965727ms)
|
||||
✔ refusals (1.956799ms)
|
||||
✔ systemVars flattens the validated config (2.45212ms)
|
||||
✔ precedence: system, business, project, project role, agent (5.70555ms)
|
||||
✔ limits narrow the definition and never widen it (3.544322ms)
|
||||
✔ role.launch stays within-role only for the instance the launch block names (10.271448ms)
|
||||
✔ limits.authority without role.launch leaves the launcher with no launch block (2.045391ms)
|
||||
✔ limits.authority narrows cross-role actions too (1.293584ms)
|
||||
✔ classify (1.654218ms)
|
||||
✔ the record carries what the broker and launcher need (1.433063ms)
|
||||
✔ digest: key order doesn't matter, any value change does (6.977704ms)
|
||||
✔ refusals (2.51302ms)
|
||||
✔ the four shipped version 2 roles load (4.323345ms)
|
||||
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.436966ms)
|
||||
✔ shipped authority follows the note's table (0.851329ms)
|
||||
✔ version 1 files keep loading with no authority (1.275691ms)
|
||||
✔ the conductor policy isn't a role (0.345872ms)
|
||||
✔ a missing role file is exit 4, a symbolic link too (0.53059ms)
|
||||
✔ version 2 refusals (1.463772ms)
|
||||
✔ authority: closed vocabulary, no gated-only action, no overlap (3.369154ms)
|
||||
✔ credentials: Gitea scopes (1.145651ms)
|
||||
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.190656ms)
|
||||
✔ credentials: services (0.652177ms)
|
||||
✔ contract: a non-empty regular Markdown file beside the role file (0.709878ms)
|
||||
✔ every key names known layers and a merge rule (1.255592ms)
|
||||
✔ unknown keys and wrong layers refuse (0.874063ms)
|
||||
✔ types (2.180876ms)
|
||||
✔ merge: defaults, then the most specific layer wins (0.297692ms)
|
||||
✔ merge: limits only narrow, and provenance lists each source (0.339955ms)
|
||||
✔ merge doesn't change its inputs (0.137543ms)
|
||||
ℹ tests 67
|
||||
ℹ suites 0
|
||||
ℹ pass 67
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 2113.408582
|
||||
@@ -0,0 +1,178 @@
|
||||
✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (2072.00858ms)
|
||||
✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (100.634238ms)
|
||||
✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (235.177502ms)
|
||||
✔ NO_COLOR: no escape codes, except reverse video on the selected row (309.006522ms)
|
||||
✔ 1-9 shows the decide flow's own lines and question; only y resolves (91.585101ms)
|
||||
✔ the app shows the same decide text as mosaic decide (28.681335ms)
|
||||
✔ any key but y or Y at the question answers No; nothing is sent (279.299013ms)
|
||||
✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (82.573734ms)
|
||||
✔ under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10) (386.587076ms)
|
||||
✔ a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12) (46.886626ms)
|
||||
✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (30.670179ms)
|
||||
✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (435.948796ms)
|
||||
✔ quitting at the question answers No and sends nothing, armed or not (45.043384ms)
|
||||
✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (50.279826ms)
|
||||
✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (89.895184ms)
|
||||
✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (521.491176ms)
|
||||
✔ at every size the decide box shows each line y depends on, or says it is hidden (10900.43605ms)
|
||||
✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (76.996249ms)
|
||||
✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (32.698341ms)
|
||||
✔ help or a detail box cut to fit keeps its last line, Esc included (Dewey) (17.333909ms)
|
||||
✔ keys: views, moving, trail of a selection, the command line, help and quit (76.720539ms)
|
||||
✔ one option: the status line says Press 1, not 1-1 (20.066397ms)
|
||||
✔ no business: the app says so and :business picks one (9.648616ms)
|
||||
✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (2.05813ms)
|
||||
✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (179.219948ms)
|
||||
✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.502279ms)
|
||||
✔ decide with --note still asks; a No sends nothing (10.216624ms)
|
||||
✔ the app's business comes from a live bus host only (1.131117ms)
|
||||
✔ runApp leaves the terminal as it found it on q, end of input, SIGTERM, SIGINT, SIGHUP and an error, the first frame's included (7.299085ms)
|
||||
✔ draw is pure: same state, same frame (0.856933ms)
|
||||
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (55.461037ms)
|
||||
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (67.992336ms)
|
||||
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (49.813306ms)
|
||||
✔ decide prints a declining choice as declining (54.937083ms)
|
||||
✔ an unknown outcome is reported once and never resent (37.68637ms)
|
||||
✔ a decision closed before the answer arrives exits 2 and points at its trail (31.632792ms)
|
||||
✔ a prefix that matches two open decisions exits 2 and resolves neither (35.923123ms)
|
||||
✔ without --business a command uses the live host's business, and a stale host.json is not a host (30.525435ms)
|
||||
✔ every human command refuses inside an agent run before it touches the bus (24.090293ms)
|
||||
✔ usage errors exit 4; no business and no host is a usage error (30.933137ms)
|
||||
✔ agents and tasks print through the broker (31.232584ms)
|
||||
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (11.015857ms)
|
||||
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (70.496351ms)
|
||||
✔ trackers come from the tracker.* variables of the one project that names a tracker project (215.917428ms)
|
||||
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (106.703055ms)
|
||||
✔ two projects that each name a tracker project refuse, since the boot shape holds one (70.227568ms)
|
||||
✔ a business without tracker.baseUrl gets no trackers entry (97.88874ms)
|
||||
✔ an unknown business and a broken system config refuse with exit 3 (121.096052ms)
|
||||
✔ follow prints an unsolicited message after plain talk returned (77.554448ms)
|
||||
✔ talk --follow continues after reply and prints a later unsolicited message (96.697269ms)
|
||||
✔ inbox lists all unread human messages under decisions, does not deliver or read them (539.258133ms)
|
||||
✔ follow refuses agent context and bad arguments (13.609101ms)
|
||||
✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (16.499823ms)
|
||||
✔ plain talk leaves the process SIGINT handlers unchanged (16.019852ms)
|
||||
✔ empty views say so (1.497705ms)
|
||||
✔ the trail keeps the broker's order and names a decision's task without its rows (1.729887ms)
|
||||
✔ tasks print the tracker fields the snapshot carries (0.274927ms)
|
||||
✔ the review loop: the coder pushes and asks, the reviewer fetches and approves, and the task's trail holds each step (868.055084ms)
|
||||
✔ refusals: no action, non-fast-forward, an unknown commit or request, and a push for another task moves only that task's ref (438.367228ms)
|
||||
✔ the size cap refuses a push before any byte (198.043695ms)
|
||||
✔ a fetch over the cap refuses before any byte reaches the reviewer (463.582445ms)
|
||||
✔ the socket refuses a request with no capability, an unknown one, and a bad request line (210.032646ms)
|
||||
✔ a rigged clone runs nothing: hooks, fsmonitor, a filter, a gitlink and a symlinked .git/config leave no marker (353.761068ms)
|
||||
✔ an identity with no role, or of another business, refuses not-a-role and moves no ref (286.288349ms)
|
||||
✔ a review of a project the business no longer has refuses unknown-project (282.369608ms)
|
||||
✔ past maxActive connections the socket refuses git-busy, and a hung-up connection frees its slot (182.409944ms)
|
||||
✔ close() answers host-closing to a connection still sending its request line (179.469142ms)
|
||||
✔ a bundle that trickles in refuses body-timeout at its deadline and frees its slot (609.535463ms)
|
||||
✔ a reviewer gone before its bundle was sent is filed fetch-aborted, not as the host's fault (589.750074ms)
|
||||
✔ a reviewer that hangs up the moment it has the whole bundle was served, not aborted (702.820755ms)
|
||||
✔ send: a close after every byte left is served; one that fails a queued write is fetch-aborted (43.295941ms)
|
||||
✔ a bundle with no bytes for bodyIdle refuses body-timeout long before its deadline (500.631664ms)
|
||||
✔ send: a close once the loop is past its last chunk is served, and a failed write there is fetch-aborted (47.153753ms)
|
||||
✔ letGo waits for an answer to leave for at most its cap, then destroys the socket (301.405689ms)
|
||||
✔ a record that fails and can't be undone refuses rollback-failed, which the host logs (466.111036ms)
|
||||
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1169.716156ms)
|
||||
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (201.195703ms)
|
||||
✔ a second host for the same data root refuses with exit 3 while the first runs (97.416902ms)
|
||||
✔ two bus starts racing for one data root leave one host running; the other refuses that a host already runs (621.852634ms)
|
||||
✔ a host takes over a host.lock its dead host left, and puts back a live claim that replaced it meanwhile (2.798434ms)
|
||||
✔ a host.lock that goes between the check and the read is tried again; one that can't be parsed still refuses as unreadable (1.581682ms)
|
||||
✔ a live host's lock stays, however old the file is (0.865403ms)
|
||||
✔ a host.lock left by a host killed with SIGKILL doesn't stop the next bus start (892.62938ms)
|
||||
✔ while a host boots, a reader sees no host: the human commands still ask for --business, stop and status find none (287.33747ms)
|
||||
✔ a host started before host.lock existed, holding only host.json, still refuses a second start, which leaves no lock (36.71753ms)
|
||||
✔ a broker fork that throws leaves no host.lock and no host.json (43.129665ms)
|
||||
✔ a broker send that throws leaves no host.lock and no host.json (46.465869ms)
|
||||
✔ a broker that misses its boot wait leaves no host.lock and no host.json (47.324915ms)
|
||||
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1128.599772ms)
|
||||
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (249.994234ms)
|
||||
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (121.902157ms)
|
||||
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (221.444476ms)
|
||||
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (171.661576ms)
|
||||
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (99.614814ms)
|
||||
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (164.677578ms)
|
||||
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (116.881168ms)
|
||||
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (234.581768ms)
|
||||
✔ watchChildren reports a child that died before it was called, and one that dies later (37.261431ms)
|
||||
✔ bus stop refuses to signal a live pid that is not a bus host (203.704097ms)
|
||||
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (360.322021ms)
|
||||
✔ bus start refuses with exit 3 without a notifier config (170.900262ms)
|
||||
✔ bus start runs until bus stop; status reports it while it runs (852.49524ms)
|
||||
✔ bus-service.sh renders the unit and installs it into a given directory (80.878627ms)
|
||||
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (99.109628ms)
|
||||
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (21.401316ms)
|
||||
✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (12.879847ms)
|
||||
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1503.556768ms)
|
||||
✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (539.183522ms)
|
||||
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or kept and logged (3718.76532ms)
|
||||
✔ a project clone that can't be made refuses checkout-failed, from --pm and from a session, with no run and no clone left (204.235815ms)
|
||||
✔ a runner that stops at once ends its launch with the runner's reason (303.388665ms)
|
||||
✔ a Claude Code launch with no session credential refuses before it starts (130.472907ms)
|
||||
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (249.751196ms)
|
||||
✔ a runner killed from the host ends its launch as killed, not failed (288.873926ms)
|
||||
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (127.584113ms)
|
||||
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1492.168316ms)
|
||||
✔ 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 (713.215968ms)
|
||||
✔ 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 (3664.360388ms)
|
||||
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (125.231636ms)
|
||||
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (112.559555ms)
|
||||
✔ a launch client that never closes its side doesn't hold the host's close (116.405794ms)
|
||||
✔ a runner that ignores SIGTERM is killed when the host closes (1234.416597ms)
|
||||
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (8.663611ms)
|
||||
✔ zoned uses the IANA zone across DST (28.625289ms)
|
||||
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (59.365149ms)
|
||||
✔ two blocking decisions get two DMs with different nonces (43.223205ms)
|
||||
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.702772ms)
|
||||
✔ a failed DM is journaled, backs off, and is retried until it lands (68.854145ms)
|
||||
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (61.446613ms)
|
||||
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (50.464411ms)
|
||||
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (50.985901ms)
|
||||
✔ 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 (224.407266ms)
|
||||
✔ a restart after the second refusal does not send before that refusal's 30 min are up (76.566261ms)
|
||||
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (17.019829ms)
|
||||
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (13.366576ms)
|
||||
✔ an inbox read failure is logged and the next poll retries (1.135089ms)
|
||||
✔ no Discord id reaches the journal or the log (16.671831ms)
|
||||
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.978958ms)
|
||||
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.971216ms)
|
||||
✔ the journal: a whole file that is one torn line truncates to empty (1.739887ms)
|
||||
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.285485ms)
|
||||
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.998317ms)
|
||||
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.643184ms)
|
||||
✔ the journal: a symlinked directory refuses and says it is a link (0.481149ms)
|
||||
✔ 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.565902ms)
|
||||
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.524928ms)
|
||||
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.812976ms)
|
||||
✔ digest content stays within Discord's 2000 characters (0.344114ms)
|
||||
✔ runLoop never overlaps ticks and stops after the one in flight (111.715776ms)
|
||||
✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (944.379185ms)
|
||||
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (529.563755ms)
|
||||
✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (554.888397ms)
|
||||
✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (395.727305ms)
|
||||
✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (4654.405998ms)
|
||||
✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (611.404247ms)
|
||||
✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (218.536014ms)
|
||||
✔ install never shows a template name on systemd 261; template drop-ins still refuse (203.082075ms)
|
||||
✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (188.641981ms)
|
||||
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 (530.616715ms)
|
||||
✔ the transport writes {business, verb, args} to the child and reads its JSON (89.822002ms)
|
||||
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2433.893296ms)
|
||||
✔ busExit and refuseInsideAgent (0.666436ms)
|
||||
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (114.124625ms)
|
||||
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1039.482791ms)
|
||||
✔ a late reply the Console has listed still reaches the next talk (2034.631104ms)
|
||||
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (13.392252ms)
|
||||
✔ launches off and on go to the broker and change the business's launch state (13.135837ms)
|
||||
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (15.098629ms)
|
||||
ℹ tests 168
|
||||
ℹ suites 0
|
||||
ℹ pass 168
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 16339.434897
|
||||
@@ -0,0 +1,132 @@
|
||||
✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (4.767952ms)
|
||||
✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.281314ms)
|
||||
✔ completed smoke replies and ordinary questions are idle, not human blockers (0.97937ms)
|
||||
✔ only an explicit first-line input request makes a finished reply waiting (0.231126ms)
|
||||
✔ tool activity, user text, errors and unfinished turns override attention text (0.190555ms)
|
||||
✔ STOP access failure is unknown, not absence, under a non-root identity (46.187877ms)
|
||||
✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (4.599743ms)
|
||||
✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (2.560167ms)
|
||||
✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (5.940844ms)
|
||||
✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.804372ms)
|
||||
✔ server rescans connector discovery and refuses HTTP reply without transport (45.246292ms)
|
||||
✔ connector session links and linked directories are not read (1.390899ms)
|
||||
✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (3.01471ms)
|
||||
✔ CLI print uses the relaunch notice instead of old current preview (81.2284ms)
|
||||
✔ connector owner and fixed task never inherit a native relaunch notice (3.025867ms)
|
||||
✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.538118ms)
|
||||
✔ loadConfig: missing file throws ConfigError (1.668965ms)
|
||||
✔ loadConfig: invalid JSON throws ConfigError (0.331136ms)
|
||||
✔ loadConfig: missing dataRoot throws ConfigError (0.238334ms)
|
||||
✔ loadConfig: relative dataRoot throws ConfigError (0.261987ms)
|
||||
✔ loadConfig: valid config returns dataRoot (0.760813ms)
|
||||
✔ findNewestSession: picks the newest by mtime among two files (0.518374ms)
|
||||
✔ findNewestSession: finds files in nested subdirectories (0.335545ms)
|
||||
✔ findNewestSession: returns null for a missing dir (0.134792ms)
|
||||
✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.69907ms)
|
||||
✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.401589ms)
|
||||
✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.669846ms)
|
||||
✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (1.248875ms)
|
||||
✔ deriveState: full state table (0.186397ms)
|
||||
✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.427352ms)
|
||||
✔ rule: newest entry is a tool result with no assistant text after it is working (0.322572ms)
|
||||
✔ rule: a finished ordinary turn is idle, even if it says your move (0.311761ms)
|
||||
✔ task: the first user message of the session, from text blocks (0.295764ms)
|
||||
✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.327169ms)
|
||||
✔ task: no user message in the log means null (shown as unknown), never a guess (0.321657ms)
|
||||
✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.445439ms)
|
||||
✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.555741ms)
|
||||
✔ scan: the written record carries task, workspace and activeProject (0.591238ms)
|
||||
✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.661993ms)
|
||||
✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.397506ms)
|
||||
✔ 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.083006ms)
|
||||
✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.465042ms)
|
||||
✔ loadRegistrations: a missing seatsDir gives empty lists (0.158481ms)
|
||||
✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (0.792268ms)
|
||||
✔ 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.368588ms)
|
||||
✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (0.768339ms)
|
||||
✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.840581ms)
|
||||
✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.729287ms)
|
||||
✔ scanAgent: ageSeconds is computed from the injected now (0.324262ms)
|
||||
✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.168412ms)
|
||||
✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.380405ms)
|
||||
✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.508922ms)
|
||||
✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (0.950838ms)
|
||||
✔ scan: relative boardDir throws ConfigError (0.097044ms)
|
||||
✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (77.648443ms)
|
||||
✔ CLI: missing config exits 2 with a refused: message (62.30234ms)
|
||||
✔ CLI: unknown command exits 2 (58.640921ms)
|
||||
✔ CLI: unknown --liveness value exits 2 (56.280292ms)
|
||||
✔ panesRunPi: true when any trimmed line equals 'pi' (0.264876ms)
|
||||
✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.10883ms)
|
||||
✔ tmuxIsAlive: a pane running pi is alive (0.205846ms)
|
||||
✔ tmuxIsAlive: session exists but pi has exited is not alive (0.139847ms)
|
||||
✔ tmuxIsAlive: no such tmux session is not alive (0.058071ms)
|
||||
✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.068476ms)
|
||||
✔ tmuxIsAlive: passes -L <socket> only when a socket is given (0.106461ms)
|
||||
✔ parsePanes: one pane per line, command and optional tab-separated path (0.088835ms)
|
||||
✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.108185ms)
|
||||
✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.15373ms)
|
||||
✔ loadSeen: missing file returns {} (0.202048ms)
|
||||
✔ loadSeen: invalid JSON throws ConfigError (0.345704ms)
|
||||
✔ loadSeen: a JSON array throws ConfigError (0.316486ms)
|
||||
✔ loadSeen: a non-string value throws ConfigError (0.361088ms)
|
||||
✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.56403ms)
|
||||
✔ markSeen: seen false deletes the key (0.357235ms)
|
||||
✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.324873ms)
|
||||
✔ markSeen: project containing '/' throws ConfigError (0.169694ms)
|
||||
✔ markSeen: non-boolean seen throws ConfigError (0.124029ms)
|
||||
✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.334839ms)
|
||||
✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.276858ms)
|
||||
✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.291479ms)
|
||||
✔ scanAgent: an error-state session with a matching mark is seen (0.286904ms)
|
||||
✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.652755ms)
|
||||
✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.224727ms)
|
||||
✔ 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.969546ms)
|
||||
✔ 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.851498ms)
|
||||
✔ 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.194996ms)
|
||||
✔ isLoopbackHost: recognizes loopback hosts (1.343615ms)
|
||||
✔ isLoopbackHost: rejects non-loopback hosts (4.715578ms)
|
||||
✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (3.836008ms)
|
||||
✔ startServer: serves page, healthz, and a rescanning /api/board (41.664241ms)
|
||||
✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (8.445873ms)
|
||||
✔ startServer: /api/board returns 500 JSON with an error field when scan throws (2.598433ms)
|
||||
✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (65.866056ms)
|
||||
✔ CLI: serve rejects a non-numeric --port with exit 2 (61.730973ms)
|
||||
✔ CLI: scan still works after the async cli refactor (60.925597ms)
|
||||
✔ CLI: live serve prints its URL and answers /healthz (71.14012ms)
|
||||
✔ page.html: esc() escapes every HTML-significant character (0.757074ms)
|
||||
✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (9.383862ms)
|
||||
✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (2.191753ms)
|
||||
✔ POST /api/seen with invalid JSON returns 400 (3.32665ms)
|
||||
✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (2.686873ms)
|
||||
✔ POST /api/seen with a missing agent returns 400 (1.472859ms)
|
||||
✔ POST /api/board returns 405; PUT /api/seen returns 405 (1.815436ms)
|
||||
✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (57.161022ms)
|
||||
✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.496157ms)
|
||||
✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.335945ms)
|
||||
✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.210593ms)
|
||||
✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.186237ms)
|
||||
✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.396158ms)
|
||||
✔ 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.718084ms)
|
||||
✔ 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 (31.563857ms)
|
||||
✔ POST /api/reply: a registration with a tmux socket adds -L <socket> (28.047491ms)
|
||||
✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (27.95034ms)
|
||||
✔ 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 (17.22769ms)
|
||||
✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (5.172769ms)
|
||||
✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.16584ms)
|
||||
✔ 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.605873ms)
|
||||
✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (2.715956ms)
|
||||
✔ 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.335853ms)
|
||||
✔ 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 (9.028473ms)
|
||||
✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (31.467008ms)
|
||||
✔ 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.813066ms)
|
||||
✔ every refusal code the reader can raise has an HTTP status (1.036682ms)
|
||||
✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (50.316655ms)
|
||||
ℹ tests 124
|
||||
ℹ suites 0
|
||||
ℹ pass 124
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 716.971935
|
||||
@@ -0,0 +1,190 @@
|
||||
✔ W1: two processes acquire the same pair at once; exactly one claim (146.930611ms)
|
||||
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (8.993434ms)
|
||||
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (3.923504ms)
|
||||
✔ W2: acquire while a claim is reserved or active refuses already-active (146.354594ms)
|
||||
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (208.607043ms)
|
||||
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (194.925741ms)
|
||||
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (25.098104ms)
|
||||
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (22.228525ms)
|
||||
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (6.25079ms)
|
||||
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (7333.129065ms)
|
||||
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (25922.919174ms)
|
||||
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (161.740097ms)
|
||||
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (92.82546ms)
|
||||
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (274.857721ms)
|
||||
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (190.595606ms)
|
||||
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (232.284532ms)
|
||||
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (47.121895ms)
|
||||
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (125.133914ms)
|
||||
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (34.6756ms)
|
||||
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (103.642268ms)
|
||||
✔ W11: the controller writes no session file; only the fake engine's own appends appear (27.102191ms)
|
||||
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (64.961479ms)
|
||||
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (55.209351ms)
|
||||
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.326167ms)
|
||||
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.271269ms)
|
||||
✔ G3: a fixture path swapped for a live path after construction is refused at bind (2.198389ms)
|
||||
✔ no shim from this file's tests is left running for the after hook (5.459201ms)
|
||||
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (342.820837ms)
|
||||
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (178.597413ms)
|
||||
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (347.911834ms)
|
||||
✔ K4: two engines; force stop one; the other survives by independent observation (2377.282442ms)
|
||||
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (206.173295ms)
|
||||
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (342.045814ms)
|
||||
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (245.668232ms)
|
||||
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4474.987632ms)
|
||||
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (496.693105ms)
|
||||
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (394.395678ms)
|
||||
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (321.79808ms)
|
||||
✔ K6: recover without proof, without confirmation, or with changed pins is refused (71.082181ms)
|
||||
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (35.950545ms)
|
||||
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (49.669843ms)
|
||||
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3038.195605ms)
|
||||
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (6.013245ms)
|
||||
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (32.838192ms)
|
||||
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (30.394261ms)
|
||||
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (64.905099ms)
|
||||
✔ R1: after a proven force stop the controller releases the scope; the shim and the unit are gone (200.454405ms)
|
||||
✔ R2: close releases the scope of a binding proven stopped, once, when the stop's own release hasn't run (317.490409ms)
|
||||
✔ R3: no release after an unavailable stop or an unverified proof: the scope and its shim stay as evidence (697.617711ms)
|
||||
✔ E1: a natural engine exit with no member left: an engine-exit stop, stopped on a proof naming the engine, and the scope released (189.015366ms)
|
||||
✔ E2: an engine exit with a tool child alive: the stop ends uncertain and nothing is released; a force stop then ends it (2271.07149ms)
|
||||
✔ E3: an engine exit and a force stop take one escalation slot, in either order (691.792566ms)
|
||||
✔ E4: a shim that doesn't answer: the engine-exit stop misses its deadline, ends uncertain and frees the slot (742.410543ms)
|
||||
✔ E5: an engine exit after a force stop ended uncertain starts no engine-exit stop; the force stop stays current (2495.222079ms)
|
||||
✔ E6: the engine-exit observation refuses a reaped process that isn't the recorded engine: another PID or start ticks (187.26217ms)
|
||||
✔ R5: the harness finds a running shim and reaps it, and never signals a shim outside a mosaic-chat- scope (Filbert N1 and N2 on #1533) (392.093696ms)
|
||||
✔ R6: releaseCohort releases nothing while the engine runs, for another invocation ID, or for a cohort without a scope (131.217082ms)
|
||||
✔ R7: controller killed after the claim is stopped, before the release: the restart releases the scope (2378.129503ms)
|
||||
✔ R8: controller killed between the two keys' stopped revisions: the restart releases the scope (252.411357ms)
|
||||
✔ R9: a release that ended unavailable is tried again on a confirmed recover, not a refused one (225.154618ms)
|
||||
✔ R10: close releases and signals nothing for a stopped binding whose proof no longer verifies (Filbert N3 on #1536) (198.89471ms)
|
||||
✔ R11: close({ killEngine: true }) after a proven stop sends no signal to the recorded engine PID; a restart releases nothing more (2280.656225ms)
|
||||
✔ R12: a restart onto a claim stopped on a boot proof releases nothing, though its scope is listed (155.678714ms)
|
||||
✔ no shim from this file's tests is left running for the after hook (4.58803ms)
|
||||
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (53.782074ms)
|
||||
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (36.474748ms)
|
||||
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (25.307044ms)
|
||||
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (38.138865ms)
|
||||
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (20.08063ms)
|
||||
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.546322ms)
|
||||
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (23.37285ms)
|
||||
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (125.799481ms)
|
||||
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (36.521914ms)
|
||||
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (26.066237ms)
|
||||
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (34.375194ms)
|
||||
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (33.649382ms)
|
||||
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (12.311614ms)
|
||||
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (30.753986ms)
|
||||
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (50.851036ms)
|
||||
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (47.239937ms)
|
||||
✔ terminal: engine control characters are made visible; a lost connection refuses submit (30.635562ms)
|
||||
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.88998ms)
|
||||
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.590211ms)
|
||||
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.691533ms)
|
||||
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.96302ms)
|
||||
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.872444ms)
|
||||
✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.640571ms)
|
||||
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (6.032751ms)
|
||||
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.731884ms)
|
||||
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.504302ms)
|
||||
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.129874ms)
|
||||
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (346.179793ms)
|
||||
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (67.57957ms)
|
||||
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (86.006204ms)
|
||||
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (145.444185ms)
|
||||
✔ H4: self-takeover is refused (17.211147ms)
|
||||
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (90.982349ms)
|
||||
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (94.317371ms)
|
||||
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (31.67904ms)
|
||||
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (85.599492ms)
|
||||
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (144.038919ms)
|
||||
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (22.315951ms)
|
||||
✔ H13: a retry with the same request ID and different text is refused (17.900285ms)
|
||||
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (144.964883ms)
|
||||
✔ H15: a revoked connection's command is refused; the revocation fence holds (72.699665ms)
|
||||
✔ H16: a second controller for the same session refuses already-active; the first is untouched (24.378812ms)
|
||||
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (72.315331ms)
|
||||
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (27.41091ms)
|
||||
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (71.343632ms)
|
||||
✔ H18: two prompts before any native output: the second refuses busy; one engine write (16.730759ms)
|
||||
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (124.970667ms)
|
||||
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.934562ms)
|
||||
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (539.575356ms)
|
||||
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (432.024677ms)
|
||||
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (392.389636ms)
|
||||
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2536.78541ms)
|
||||
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (503.790953ms)
|
||||
✔ no shim from this file's tests is left running for the after hook (6.620474ms)
|
||||
✔ a plain conversation: catalogue row, one page, CHAT-01 records (7.592304ms)
|
||||
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (2.143137ms)
|
||||
✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.21666ms)
|
||||
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (4.236618ms)
|
||||
✔ F1: an unreadable fork is never merged into another branch's history (1.988874ms)
|
||||
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (4.744954ms)
|
||||
✔ F1: a file whose entries are all unreadable shows a notice per line (2.311653ms)
|
||||
✔ F2: a truncated trailing line marks the view incomplete, not an error (2.556183ms)
|
||||
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (5.645282ms)
|
||||
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (8.143049ms)
|
||||
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (7.935778ms)
|
||||
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (8.733653ms)
|
||||
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (13.6811ms)
|
||||
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (13.928431ms)
|
||||
✔ F8: a file swapped for a symlink after the catalogue is refused (3.229625ms)
|
||||
✔ F9: registrations never add or redirect a root (3.351671ms)
|
||||
✔ F10: a header cwd naming another project is refused (5.154183ms)
|
||||
✔ F11: parentSession renders with a marker and the parent is never opened (1.107289ms)
|
||||
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (8.259848ms)
|
||||
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (3.819605ms)
|
||||
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.863842ms)
|
||||
✔ F13: compaction is a marker in place, then the retained content (1.067477ms)
|
||||
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (657.766288ms)
|
||||
✔ fragments never cut a surrogate pair and keep an empty string (8.910212ms)
|
||||
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (2.542618ms)
|
||||
✔ unknown conversations, empty files and non-Pi files refuse (3.728631ms)
|
||||
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.675879ms)
|
||||
✔ a seat directory without search permission refuses that root, not the catalogue (3.267415ms)
|
||||
✔ every page and cursor is a valid CHAT-01 record (1014.651547ms)
|
||||
✔ the engine pin holds for the installed package (2.549244ms)
|
||||
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (375.747015ms)
|
||||
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (713.557929ms)
|
||||
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (357.925912ms)
|
||||
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (99.564748ms)
|
||||
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (48.362436ms)
|
||||
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (25.054955ms)
|
||||
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.217807ms)
|
||||
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (36.577125ms)
|
||||
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (27.167271ms)
|
||||
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (120.177777ms)
|
||||
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (52.853822ms)
|
||||
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1533.56893ms)
|
||||
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (18.938548ms)
|
||||
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (73.452504ms)
|
||||
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (17.364533ms)
|
||||
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (19.863293ms)
|
||||
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (39.013934ms)
|
||||
✔ N10: fake conformance (33.471064ms)
|
||||
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (41.560872ms)
|
||||
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (23.203998ms)
|
||||
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (66.162016ms)
|
||||
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (31.820375ms)
|
||||
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (35.948029ms)
|
||||
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (27.844473ms)
|
||||
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (19.369148ms)
|
||||
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (32.814299ms)
|
||||
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (123.240805ms)
|
||||
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (141.403031ms)
|
||||
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (95.11521ms)
|
||||
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (20.341782ms)
|
||||
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (18.079435ms)
|
||||
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (17.776104ms)
|
||||
✔ 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 (58.471507ms)
|
||||
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (44.035001ms)
|
||||
ℹ tests 182
|
||||
ℹ suites 0
|
||||
ℹ pass 182
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 37273.282406
|
||||
@@ -0,0 +1,186 @@
|
||||
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.816316ms)
|
||||
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.727877ms)
|
||||
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.918382ms)
|
||||
✔ approvals: a button approves only on its own request message with the matching custom id (0.548916ms)
|
||||
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (22.21236ms)
|
||||
✔ 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.443801ms)
|
||||
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (8.399527ms)
|
||||
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.310883ms)
|
||||
✔ authorize: open channel, listed user (1.958327ms)
|
||||
✔ authorize: wrong guild (0.244141ms)
|
||||
✔ authorize: no guild (DM) (0.304288ms)
|
||||
✔ authorize: unlisted channel (0.197898ms)
|
||||
✔ authorize: unknown channel, no info (0.264286ms)
|
||||
✔ authorize: thread of listed parent (0.208027ms)
|
||||
✔ authorize: thread of unlisted parent (0.284588ms)
|
||||
✔ authorize: text channel that is not a thread and not listed (0.146496ms)
|
||||
✔ authorize: unlisted user (1.379185ms)
|
||||
✔ authorize: no author (0.556372ms)
|
||||
✔ authorize: bot author (listed id, bot flag) (0.262398ms)
|
||||
✔ authorize: system author (0.386836ms)
|
||||
✔ authorize: the bot itself (0.092062ms)
|
||||
✔ authorize: webhook (0.101587ms)
|
||||
✔ authorize: mention channel without mention (0.775098ms)
|
||||
✔ authorize: mention channel with bot mention (0.160531ms)
|
||||
✔ authorize: mention channel with @everyone only (0.248339ms)
|
||||
✔ authorize: mention channel mentioning someone else (0.093945ms)
|
||||
✔ authorize: mention channel, content says @bot but mentions empty (0.118155ms)
|
||||
✔ authorize: private thread under mention channel, mentioned (0.113156ms)
|
||||
✔ authorize: private thread under mention channel, not mentioned (0.085636ms)
|
||||
✔ authorize: thread in another guild per channel info (0.096506ms)
|
||||
✔ authorize: not an object (0.069926ms)
|
||||
✔ authorize: no id (0.07224ms)
|
||||
✔ authorize: oversize content is accepted and flagged (0.075698ms)
|
||||
✔ authorize: exactly the limit is not oversize (0.074892ms)
|
||||
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.577524ms)
|
||||
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.206481ms)
|
||||
✔ binding: a complete binding validates and is frozen (2.973566ms)
|
||||
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.667701ms)
|
||||
✔ binding: empty allowlists refuse (0.64121ms)
|
||||
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.444577ms)
|
||||
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.11361ms)
|
||||
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.72356ms)
|
||||
✔ binding: file must be 0600, regular, not a symlink (2.032381ms)
|
||||
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (2.081902ms)
|
||||
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (125.549417ms)
|
||||
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.941551ms)
|
||||
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (420.652887ms)
|
||||
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (263.64633ms)
|
||||
✔ cli: run refuses when STOP is present, before any network use (162.422253ms)
|
||||
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.08669ms)
|
||||
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.410586ms)
|
||||
✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (22.343766ms)
|
||||
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.864269ms)
|
||||
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.84773ms)
|
||||
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.534152ms)
|
||||
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.880485ms)
|
||||
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.450274ms)
|
||||
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.150781ms)
|
||||
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.934022ms)
|
||||
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.495656ms)
|
||||
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.269205ms)
|
||||
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.377017ms)
|
||||
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.808353ms)
|
||||
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.512292ms)
|
||||
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.451802ms)
|
||||
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (1.608565ms)
|
||||
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.079553ms)
|
||||
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.426231ms)
|
||||
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.906739ms)
|
||||
✔ 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.333447ms)
|
||||
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.294965ms)
|
||||
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.185002ms)
|
||||
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.892879ms)
|
||||
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.42452ms)
|
||||
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.804186ms)
|
||||
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.901463ms)
|
||||
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.336968ms)
|
||||
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.316132ms)
|
||||
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.810896ms)
|
||||
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.806731ms)
|
||||
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.869412ms)
|
||||
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.933698ms)
|
||||
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.519594ms)
|
||||
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (61.946487ms)
|
||||
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (55.397941ms)
|
||||
✔ engine: one prompt, one turn, text and usage come back (50.994597ms)
|
||||
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (347.753675ms)
|
||||
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (236.924929ms)
|
||||
✔ engine: timeout sends abort and fails only that turn; the process stays (106.83428ms)
|
||||
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (234.937544ms)
|
||||
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (129.539142ms)
|
||||
✔ 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.553849ms)
|
||||
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.143941ms)
|
||||
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.722167ms)
|
||||
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.577587ms)
|
||||
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.406002ms)
|
||||
✔ engine: a malformed JSONL line fails the turn, not the process (33.48332ms)
|
||||
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (45.860958ms)
|
||||
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.764099ms)
|
||||
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.914086ms)
|
||||
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.780923ms)
|
||||
✔ gateway: op 9 resumable resumes (0.397508ms)
|
||||
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.973312ms)
|
||||
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.652392ms)
|
||||
✔ gateway: close() is final and unparseable frames are ignored (0.622101ms)
|
||||
✔ git: config validation is strict, needs write: true, a work tree and a private token file (80.476703ms)
|
||||
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (61.779717ms)
|
||||
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (96.671048ms)
|
||||
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.438352ms)
|
||||
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (112.423757ms)
|
||||
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (108.666948ms)
|
||||
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (111.074015ms)
|
||||
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (273.470356ms)
|
||||
✔ git: push pushes the named branch only and reports up to date (87.310687ms)
|
||||
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (135.964284ms)
|
||||
✔ git: the credential helper answers get over https from a private file and nothing else (208.789981ms)
|
||||
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (806.925827ms)
|
||||
✔ lock: the claim is exclusive; a second start against a live owner refuses (5.308721ms)
|
||||
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (87.437169ms)
|
||||
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.013045ms)
|
||||
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.100811ms)
|
||||
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (48.483745ms)
|
||||
✔ 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 (373.955178ms)
|
||||
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.679486ms)
|
||||
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (29.514014ms)
|
||||
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (4.644648ms)
|
||||
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (55.973938ms)
|
||||
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (182.353333ms)
|
||||
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (107.014484ms)
|
||||
✔ notices: a kind is recorded per UTC day and found again (0.553487ms)
|
||||
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.987855ms)
|
||||
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (3.500879ms)
|
||||
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (3.664381ms)
|
||||
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (59.687106ms)
|
||||
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (45.716101ms)
|
||||
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (44.075203ms)
|
||||
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (88.078801ms)
|
||||
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (835.094971ms)
|
||||
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.648989ms)
|
||||
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.516953ms)
|
||||
✔ rest: content and nonce limits are enforced locally; typing never throws (0.752239ms)
|
||||
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.796043ms)
|
||||
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.986171ms)
|
||||
✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.83705ms)
|
||||
✔ setspark config: reaches the tools config and the binding as a fixed key (3.533845ms)
|
||||
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.839622ms)
|
||||
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.848252ms)
|
||||
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (25.905349ms)
|
||||
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (10.34452ms)
|
||||
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.287348ms)
|
||||
✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.186139ms)
|
||||
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.508582ms)
|
||||
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (2.227386ms)
|
||||
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1013.664829ms)
|
||||
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.54705ms)
|
||||
✔ 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.596165ms)
|
||||
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.683469ms)
|
||||
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.308244ms)
|
||||
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.679031ms)
|
||||
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.207281ms)
|
||||
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.379021ms)
|
||||
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.388491ms)
|
||||
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.412317ms)
|
||||
✔ tools: listing and search caps hold (11.737783ms)
|
||||
✔ tools: credential shapes are caught; ordinary prose and ids are not (0.877391ms)
|
||||
✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (9.119288ms)
|
||||
✔ tools: an unreadable file under the root is skipped by search and refused by read (1.881803ms)
|
||||
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.524118ms)
|
||||
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (7.661962ms)
|
||||
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (5.571929ms)
|
||||
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.949788ms)
|
||||
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.724227ms)
|
||||
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.289164ms)
|
||||
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1031.142648ms)
|
||||
✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (5.118677ms)
|
||||
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.346833ms)
|
||||
✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (3.044836ms)
|
||||
✔ 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.611993ms)
|
||||
ℹ tests 178
|
||||
ℹ suites 0
|
||||
ℹ pass 178
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 2725.477078
|
||||
@@ -1,82 +1,82 @@
|
||||
✔ sessionModel: agent vars win, then the system's execution settings (12.417381ms)
|
||||
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (6.775936ms)
|
||||
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (3.193608ms)
|
||||
✔ the prompt's built-in tools line is the harness's own names, as the manifest lists them (12.569584ms)
|
||||
✔ the prompt names the project clones in the workspace, and none when there are none (9.16228ms)
|
||||
✔ the prompt explains the review loop to a role with push_working, and reviews to one with fetch_review (10.201009ms)
|
||||
✔ a bundle is written once: an existing file refuses (2.631683ms)
|
||||
✔ a path with a single quote can't go into the hook command (2.440308ms)
|
||||
✔ allow exits 0, a deny exits 2 with the reason on stderr (153.578839ms)
|
||||
✔ a missing or wrong policy, or a bad event, exits 2 (95.04587ms)
|
||||
✔ the bundle's wrapped command: a missing gate or node still blocks (1120.585558ms)
|
||||
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (898.889587ms)
|
||||
✔ claude: the hook alone blocks a path outside the workspace (622.876249ms)
|
||||
✔ claude: a second turn resumes the first turn's session (979.48539ms)
|
||||
✔ claude adapter: --restricted is always passed (7.782982ms)
|
||||
✔ claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable (30.908728ms)
|
||||
✔ claude adapter: a directory pin exits 2 before it runs; a pin with a space runs (10.149509ms)
|
||||
✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (1244.844694ms)
|
||||
✔ claude: a missing hook or MCP file refuses before claude starts (13.654284ms)
|
||||
✔ pi: policy tools and typed tools pass, anything else is blocked (4.589988ms)
|
||||
✔ claude: builtins map from pi names, typed tools need the mcp prefix (1.054042ms)
|
||||
✔ file tool paths must resolve inside the workspace (2.167273ms)
|
||||
✔ pi's own path normalisation can't be used to step out (1.5888ms)
|
||||
✔ a symlink inside the workspace that points out is outside (1.29361ms)
|
||||
✔ a dangling symlink is refused at any depth, in both harnesses (4.585817ms)
|
||||
✔ read is checked under every spelling pi's read would open, in both harnesses (21.533924ms)
|
||||
✔ other spellings cover directories, dangling links and pi's cwd (2.312075ms)
|
||||
✔ a relative path climbs from the workspace's real path, in both harnesses (6.537201ms)
|
||||
✔ claude path fields per tool (1.068644ms)
|
||||
✔ glob patterns stay inside the workspace (0.989193ms)
|
||||
✔ a path that can't be checked is blocked (0.54534ms)
|
||||
✔ fetch_review's have lists the working refs before origin's, and no more than HAVE_MAX (281.931805ms)
|
||||
✔ fetch_review's have orders the working refs newest first within each clone, one clone after another (199.917049ms)
|
||||
✔ a runner git past its time: SIGTERM reaches its process group, and SIGKILL follows after the grace period (1810.425036ms)
|
||||
✔ once a signalled runner git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (305.978604ms)
|
||||
✔ initialize, ping and tools/list (56.659114ms)
|
||||
✔ tools/call goes through the tool socket; a refusal is an isError result (38.511849ms)
|
||||
✔ replies are compact JSON; over the cap a tool error names the size and the cap (38.453315ms)
|
||||
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (35.634665ms)
|
||||
✔ a missing argument is a usage error (34.545934ms)
|
||||
✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (478.514817ms)
|
||||
✔ pi: a reply over the cap is a tool error naming the size and the cap; list_tasks asks for open tasks (398.769327ms)
|
||||
✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (473.025619ms)
|
||||
✔ pi: a read is refused when pi would open another spelling outside (492.946075ms)
|
||||
✔ pi: a relative path climbs from the real path of a workspace behind a symlink (408.34998ms)
|
||||
✔ pi: a relative path climbs from the real path of a dataRoot behind a symlink (505.304754ms)
|
||||
✔ pi: a missing extension refuses before any model call (10.805917ms)
|
||||
✔ pi: an extension without its configuration fails pi's start (379.830008ms)
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (1.385847ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.237243ms)
|
||||
✔ turnRequest shows the message's own sent time, a day old as well as new (0.145817ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.250355ms)
|
||||
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (194.770986ms)
|
||||
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (201.810113ms)
|
||||
✔ a task closed on the tracker is missing from the next turn's open list (199.027864ms)
|
||||
✔ a message read a day after it was sent shows the time it was sent (118.425302ms)
|
||||
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (65.822541ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (60.525033ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (331.868858ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1291.178798ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (132.669006ms)
|
||||
✔ founder credentials stop before the claim (20) (126.496724ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (134.206179ms)
|
||||
✔ the launch ending under a running session exits 22 (131.363335ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (169.977483ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (71.616218ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (242.257316ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (160.454735ms)
|
||||
✔ the PM gets launch, its task verbs and the reads (9.750552ms)
|
||||
✔ list_requirements reads the requirement ids, for a task.create holder with a tracker (4.999651ms)
|
||||
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.97782ms)
|
||||
✔ a coder with a tracker gets its own task tools and no task_reassign (1.807452ms)
|
||||
✔ launch only when the business's launch block names the instance as launcher (2.467031ms)
|
||||
✔ an action outside the instance's authority has no tool (2.321265ms)
|
||||
✔ the review loop: push_working and request_review for the coder, fetch_review and review_verdict for the reviewer, none for the PM (7.019703ms)
|
||||
✔ callTool: one JSON line out, the result back, a refusal rejects (11.392769ms)
|
||||
✔ list_tasks takes the broker's filters and pages; get_task takes a task_ref (3.6841ms)
|
||||
✔ callTool sends list_tasks done false unless the session names done; other tools as given (3.669431ms)
|
||||
✔ replyText: compact JSON up to the cap, an error naming the size and the cap over it (1.135804ms)
|
||||
✔ sessionModel: agent vars win, then the system's execution settings (15.958113ms)
|
||||
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (9.103047ms)
|
||||
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (4.756058ms)
|
||||
✔ the prompt's built-in tools line is the harness's own names, as the manifest lists them (13.306042ms)
|
||||
✔ the prompt names the project clones in the workspace, and none when there are none (8.273247ms)
|
||||
✔ the prompt explains the review loop to a role with push_working, and reviews to one with fetch_review (9.893614ms)
|
||||
✔ a bundle is written once: an existing file refuses (2.29674ms)
|
||||
✔ a path with a single quote can't go into the hook command (2.724104ms)
|
||||
✔ allow exits 0, a deny exits 2 with the reason on stderr (169.096943ms)
|
||||
✔ a missing or wrong policy, or a bad event, exits 2 (117.200518ms)
|
||||
✔ the bundle's wrapped command: a missing gate or node still blocks (1122.19007ms)
|
||||
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (905.258781ms)
|
||||
✔ claude: the hook alone blocks a path outside the workspace (575.276044ms)
|
||||
✔ claude: a second turn resumes the first turn's session (847.707487ms)
|
||||
✔ claude adapter: --restricted is always passed (6.203057ms)
|
||||
✔ claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable (22.996141ms)
|
||||
✔ claude adapter: a directory pin exits 2 before it runs; a pin with a space runs (8.007834ms)
|
||||
✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (811.735775ms)
|
||||
✔ claude: a missing hook or MCP file refuses before claude starts (9.641035ms)
|
||||
✔ pi: policy tools and typed tools pass, anything else is blocked (5.065197ms)
|
||||
✔ claude: builtins map from pi names, typed tools need the mcp prefix (1.510078ms)
|
||||
✔ file tool paths must resolve inside the workspace (2.265902ms)
|
||||
✔ pi's own path normalisation can't be used to step out (1.849642ms)
|
||||
✔ a symlink inside the workspace that points out is outside (1.238834ms)
|
||||
✔ a dangling symlink is refused at any depth, in both harnesses (4.007724ms)
|
||||
✔ read is checked under every spelling pi's read would open, in both harnesses (23.752079ms)
|
||||
✔ other spellings cover directories, dangling links and pi's cwd (2.601822ms)
|
||||
✔ a relative path climbs from the workspace's real path, in both harnesses (7.878381ms)
|
||||
✔ claude path fields per tool (0.741323ms)
|
||||
✔ glob patterns stay inside the workspace (0.683896ms)
|
||||
✔ a path that can't be checked is blocked (0.397279ms)
|
||||
✔ fetch_review's have lists the working refs before origin's, and no more than HAVE_MAX (338.972717ms)
|
||||
✔ fetch_review's have orders the working refs newest first within each clone, one clone after another (186.04942ms)
|
||||
✔ a runner git past its time: SIGTERM reaches its process group, and SIGKILL follows after the grace period (1809.1766ms)
|
||||
✔ once a signalled runner git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (302.915618ms)
|
||||
✔ initialize, ping and tools/list (62.683791ms)
|
||||
✔ tools/call goes through the tool socket; a refusal is an isError result (55.354418ms)
|
||||
✔ replies are compact JSON; over the cap a tool error names the size and the cap (45.209158ms)
|
||||
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (45.635052ms)
|
||||
✔ a missing argument is a usage error (50.614831ms)
|
||||
✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (509.647216ms)
|
||||
✔ pi: a reply over the cap is a tool error naming the size and the cap; list_tasks asks for open tasks (387.538158ms)
|
||||
✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (425.409913ms)
|
||||
✔ pi: a read is refused when pi would open another spelling outside (402.971976ms)
|
||||
✔ pi: a relative path climbs from the real path of a workspace behind a symlink (381.42415ms)
|
||||
✔ pi: a relative path climbs from the real path of a dataRoot behind a symlink (361.421306ms)
|
||||
✔ pi: a missing extension refuses before any model call (6.811744ms)
|
||||
✔ pi: an extension without its configuration fails pi's start (287.165763ms)
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (1.864169ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.319807ms)
|
||||
✔ turnRequest shows the message's own sent time, a day old as well as new (0.238948ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.439617ms)
|
||||
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (226.067839ms)
|
||||
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (224.763068ms)
|
||||
✔ a task closed on the tracker is missing from the next turn's open list (197.780503ms)
|
||||
✔ a message read a day after it was sent shows the time it was sent (114.703483ms)
|
||||
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (68.06901ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (53.279347ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (327.227072ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1279.169623ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (119.740609ms)
|
||||
✔ founder credentials stop before the claim (20) (110.110443ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (98.484436ms)
|
||||
✔ the launch ending under a running session exits 22 (100.500183ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (225.153765ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (56.173475ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (172.453793ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (91.943478ms)
|
||||
✔ the PM gets launch, its task verbs and the reads (12.755559ms)
|
||||
✔ list_requirements reads the requirement ids, for a task.create holder with a tracker (5.613463ms)
|
||||
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (3.444317ms)
|
||||
✔ a coder with a tracker gets its own task tools and no task_reassign (2.195961ms)
|
||||
✔ launch only when the business's launch block names the instance as launcher (2.407716ms)
|
||||
✔ an action outside the instance's authority has no tool (2.558129ms)
|
||||
✔ the review loop: push_working and request_review for the coder, fetch_review and review_verdict for the reviewer, none for the PM (11.218332ms)
|
||||
✔ callTool: one JSON line out, the result back, a refusal rejects (14.150176ms)
|
||||
✔ list_tasks takes the broker's filters and pages; get_task takes a task_ref (3.756039ms)
|
||||
✔ callTool sends list_tasks done false unless the session names done; other tools as given (4.695057ms)
|
||||
✔ replyText: compact JSON up to the cap, an error naming the size and the cap over it (1.425814ms)
|
||||
ℹ tests 79
|
||||
ℹ suites 0
|
||||
ℹ pass 79
|
||||
@@ -84,4 +84,4 @@
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 10721.559659
|
||||
ℹ duration_ms 10763.623244
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
✔ a raw token file, with or without one trailing newline, reaches curl only through the config stream (129.085084ms)
|
||||
✔ the raw path accepts nothing else, and refuses before curl runs (429.369605ms)
|
||||
✔ the file checks still apply on the raw path: mode, symlink, missing, directory (335.139647ms)
|
||||
✔ the raw path base URL has no override (67.429461ms)
|
||||
✔ the JSON path is unchanged, and JSON never falls through to the raw path (260.94244ms)
|
||||
✔ a file that changes between the two reads refuses before curl runs, with or without a body (813.295333ms)
|
||||
✔ the token reaches no child environment, even with an inherited CFG or SHELLOPTS=allexport (854.214622ms)
|
||||
✔ real helper GET HTTP 200 preserves exit 0 without credentials (14.988708ms)
|
||||
✔ real helper POST HTTP 201 preserves exit 0 without credentials (11.058557ms)
|
||||
✔ real helper GET HTTP 403 preserves exit 1 without credentials (9.223198ms)
|
||||
✔ fixture git subjects only, follow-ups and three session kinds (116.339132ms)
|
||||
✔ text and JSON carry same numbers, open and truncated title (206.27205ms)
|
||||
✔ missing credentials exit 2, no-issues never calls API and shows unknown (159.326549ms)
|
||||
✔ empty range gives no rows and zero totals (114.586556ms)
|
||||
✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (156.171903ms)
|
||||
✔ close-only issue included, even median, missing metadata stays unknown (116.631057ms)
|
||||
✔ unique commits but per-issue links count multiple tags once each (111.946812ms)
|
||||
✔ page cap refuses rather than silently undercounting (98.404923ms)
|
||||
✔ bad API payload not JSON refuses (94.729805ms)
|
||||
✔ bad API payload {} refuses (92.275154ms)
|
||||
✔ bad API payload [{"number":1}] refuses (106.603678ms)
|
||||
✔ partial or malformed session log refuses with location, not content (126.128021ms)
|
||||
✔ a U+2028 or U+2029 inside a session string is one line, not a malformed record (114.393581ms)
|
||||
✔ no sessions is an empty table; symlink source refuses (224.128788ms)
|
||||
✔ reads only refactor even when another branch is checked out (120.675824ms)
|
||||
✔ invalid dates, reverse dates and duplicate options refuse (90.527635ms)
|
||||
✔ T3 agent assignments do not count as human in Table 2 (138.9424ms)
|
||||
✔ preamble parsing and issue number boundaries (0.688983ms)
|
||||
✔ T3 header: agent, or board from control-board; anything short of the full header is human (0.232726ms)
|
||||
✔ no closed issues with human messages means undefined ratio, not invented zero (0.278463ms)
|
||||
✔ T3: seat, archived, unmapped and Researcher threads count; imported, deleted and other-project threads do not (240.300479ms)
|
||||
✔ T3: the default path is read from HOME and prints no path line; --no-t3 says so (499.23794ms)
|
||||
✔ T3: a HOME with no database exits 1 and names --no-t3 (101.834405ms)
|
||||
✔ T3: a file that is not a database exits 1 and names --no-t3 (115.061387ms)
|
||||
✔ T3: a seat thread renamed to another seat exits 1 naming thread, title and roles (133.583891ms)
|
||||
✔ T3: an unmapped thread addressed as a seat exits 1 (161.899289ms)
|
||||
✔ T3: a header to another thread id is not cross-checked (142.149391ms)
|
||||
✔ T3: no project, or two, for this root exits 1 (348.075046ms)
|
||||
✔ T3: a removed column exits 1 and names it (116.764986ms)
|
||||
✔ T3: a missing table exits 1 and names it (112.862533ms)
|
||||
✔ T3: a counted row with an unknown role exits 1 without its text (119.150248ms)
|
||||
✔ T3: a counted row with non-text content exits 1 without its text (126.166512ms)
|
||||
✔ T3: a counted row with an unparseable created_at exits 1 without its text (125.318149ms)
|
||||
✔ T3: a missing orchestration_events makes the diagnostic unknown and keeps the counts (244.960257ms)
|
||||
✔ T3: a human message with no event counts in humanWithoutEvent (142.266089ms)
|
||||
✔ T3: an unparseable event makes the diagnostic unknown and keeps the counts (221.500243ms)
|
||||
✔ T3: an event with no string messageId makes the diagnostic unknown and keeps the counts (235.548555ms)
|
||||
✔ T3: an error that is not from SQLite is rethrown, not reported as a database failure (65.613953ms)
|
||||
✔ T3: a symlink at ~/.t3 exits 1 (99.90199ms)
|
||||
✔ T3: a symlink at ~/.t3/userdata exits 1 (98.557825ms)
|
||||
✔ T3: a symlink at ~/.t3/userdata/state.sqlite exits 1 (100.391127ms)
|
||||
✔ T3: with --t3-db, a symlinked file or directory exits 1 (198.952676ms)
|
||||
✔ T3 WAL: the newest message only in -wal, writer attached, is counted (169.727936ms)
|
||||
✔ T3 WAL: stopped cleanly, counts are correct and the main file is unchanged (135.500301ms)
|
||||
✔ T3 WAL: -wal without -shm in a writable directory is read (156.748767ms)
|
||||
✔ T3 WAL: -wal without -shm in a read-only directory exits 1 (150.316494ms)
|
||||
✔ T3 WAL: stopped cleanly in a read-only directory exits 1 (121.822212ms)
|
||||
✔ T3: a lock held past the 5 s busy timeout exits 1 and names --no-t3 (5128.128868ms)
|
||||
✔ a done row whose closing issue is open is a violation; a row that is not done is not (1.827737ms)
|
||||
✔ an issue several rows close is expected closed only once all of them are done (0.552686ms)
|
||||
✔ closure needs positive evidence: unknown is undecided, and so is a skipped or short issue check (0.404435ms)
|
||||
✔ each owner of an in-progress or in-review row gets one liveness class (7.951769ms)
|
||||
✔ a required row not done after 14 days is a violation; a legacy row uses genesis as its lower bound (0.397829ms)
|
||||
✔ an ISO requiredSince, as `set required` writes it, ages from its UTC day; one that does not parse is a violation (0.236898ms)
|
||||
✔ the text section always ends in a count and a result, and never prints a full pass (0.355521ms)
|
||||
✔ pidAlive: a running pid is present, an exited one is gone, and EPERM still means present (30.851552ms)
|
||||
✔ issue states: open list first, then the metric page, then at most 10 lookups (324.914482ms)
|
||||
✔ a full open list: lookups settle what it leaves out, and only an unsettled issue keeps it undecided (448.545569ms)
|
||||
✔ the open list refuses on a failed call or a bad record, and never echoes the helper (191.343928ms)
|
||||
✔ a helper call past the deadline is killed with its child, and the call reports it (2009.786474ms)
|
||||
✔ readQueue loads queue.json through the queue validator and refuses anything else (113.753784ms)
|
||||
✔ protected changes list every in-range entry that changes a required or parked row (320.392907ms)
|
||||
✔ the CLI prints the queue section above the weekly table and under a queue key in --json (559.843743ms)
|
||||
✔ a queue with nothing wrong prints 0 violations and a reduced pass, never a full pass (213.889311ms)
|
||||
✔ --no-issues makes no call and leaves the issue checks undecided; --no-queue skips the section (291.184926ms)
|
||||
✔ the CLI refuses a bad queue before any call, and a failed open list with exit 2 (285.025361ms)
|
||||
✔ --unsupported-runtime repeats once per seat and takes a seat name (333.473007ms)
|
||||
✔ an unreadable config makes every owner invalid instead of passing them (142.895105ms)
|
||||
ℹ tests 78
|
||||
ℹ suites 0
|
||||
ℹ pass 78
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 11860.725626
|
||||
@@ -0,0 +1,77 @@
|
||||
✔ pure resolution selects current default or explicit enrolled account (2.317463ms)
|
||||
✔ scope is explicit, bounded and never inferred (1.970551ms)
|
||||
✔ fork pin is preserved against default change, override, missing account and revocation (0.93547ms)
|
||||
✔ unenrolled account/provider, missing harness, model expansion and native model ceiling refuse (1.103232ms)
|
||||
✔ only explicit synthetic credential forms and internal fixture stores admitted (7.07295ms)
|
||||
✔ two concurrent workspaces of the same agent publish distinct complete private generations (46.398973ms)
|
||||
✔ same execution ID is exclusively claimed and cannot overwrite a published generation (37.318776ms)
|
||||
✔ failed generation after-auth preserves prior files, records failure and refuses blind same-ID retry (50.795063ms)
|
||||
✔ failed generation before-publish preserves prior files, records failure and refuses blind same-ID retry (64.982126ms)
|
||||
✔ credential lock contention refuses without duplicate side effects (12.438588ms)
|
||||
✔ symlinked pre-existing final target is refused and never followed (29.395738ms)
|
||||
✔ invalid registry cannot resolve; no fallback to supplied partial entries (0.360708ms)
|
||||
✔ post-publication failure records uncertainty, preserves complete generation and prevents replay (32.184787ms)
|
||||
✔ expired credentials refresh under transaction and subsequent generation reuses rotation (97.467733ms)
|
||||
✔ refresh failure retains prior generation and store state (68.090002ms)
|
||||
✔ refresh timeout retains prior generation and store state (151.819704ms)
|
||||
✔ refresh malformed retains prior generation and store state (68.685012ms)
|
||||
✔ concurrent refresh on same account refuses contention while unrelated account proceeds (228.08922ms)
|
||||
✔ invalid refresh options refuse before burning claim (32.841467ms)
|
||||
✔ fixed fake process rotates both OAuth fields without mutating caller input (32.760438ms)
|
||||
✔ concurrent isolated processes preserve separate provider credentials (32.125987ms)
|
||||
✔ fake failure is refused with fixed diagnostics (30.124111ms)
|
||||
✔ fake malformed is refused with fixed diagnostics (29.982969ms)
|
||||
✔ fake timeout is refused with fixed diagnostics (103.836805ms)
|
||||
✔ fake unchanged is refused with fixed diagnostics (30.383328ms)
|
||||
✔ caller executable/environment injection is rejected before spawning (0.420021ms)
|
||||
✔ valid fixture tree validates and lists without secrets (80.547898ms)
|
||||
✔ unknown-field refuses (0.451744ms)
|
||||
✔ invalid-id refuses uppercase and traversal shapes (0.345242ms)
|
||||
✔ plain-http baseUrl requires allowInsecureTransport (0.387426ms)
|
||||
✔ native provider rejects allowInsecureTransport (0.169847ms)
|
||||
✔ unsupported credential type and kind refuse (0.229278ms)
|
||||
✔ account provider-path mismatch refuses (0.162216ms)
|
||||
✔ profile account refs must be provider/account shaped (0.270433ms)
|
||||
✔ seat selection accepts fork pin field, validates account refs (0.263439ms)
|
||||
✔ harness manifest id must equal executable (gate 1) (0.237158ms)
|
||||
✔ CLI validate: duplicate provider id across files refuses (36.977774ms)
|
||||
✔ CLI validate: missing referenced provider/account refuse (38.044792ms)
|
||||
✔ CLI validate: broken JSON refuses without secret echo (30.04892ms)
|
||||
✔ CLI usage errors exit 2 (60.181708ms)
|
||||
✔ credential.json sibling presence does not break validation and is never read (73.157794ms)
|
||||
✔ D1 missing, empty and structurally empty roots refuse, no list projection (216.829167ms)
|
||||
✔ D1 required directory auth cannot be absent (74.176184ms)
|
||||
✔ D1 required directory auth/providers cannot be absent (65.192191ms)
|
||||
✔ D1 required directory auth/accounts cannot be absent (71.285272ms)
|
||||
✔ D1 required directory auth/settings cannot be absent (68.125713ms)
|
||||
✔ D1 required directory harnesses cannot be absent (69.810606ms)
|
||||
✔ D1 root file and unreadable metadata refuse (114.295078ms)
|
||||
✔ D2 no symlink traversal at auth/providers/openai-codex.json (61.888471ms)
|
||||
✔ D2 no symlink traversal at auth/accounts/openai-codex/homelab-openai (65.679802ms)
|
||||
✔ D2 no symlink traversal at auth/providers (57.096974ms)
|
||||
✔ D2 no symlink traversal at auth (59.007393ms)
|
||||
✔ D2 root and ancestor symlinks and lexical traversal refuse (162.836674ms)
|
||||
✔ private filesystem modes enforced for root (55.439729ms)
|
||||
✔ private filesystem modes enforced for auth (54.690311ms)
|
||||
✔ private filesystem modes enforced for auth/providers/openai-codex.json (56.414831ms)
|
||||
✔ private filesystem modes enforced for auth/accounts/openai-codex/homelab-openai/account.json (64.508921ms)
|
||||
✔ D3 numeric version 1 only across all record kinds (1.316678ms)
|
||||
✔ D4 nested unknown keys and missing per-kind required fields refuse (63.477386ms)
|
||||
✔ D5 unenrolled default refuses even when account exists (69.864198ms)
|
||||
✔ D6 provider/account credential type must match (68.943329ms)
|
||||
✔ D7 every harness endpoint enforces HTTP opt-in and shape (0.555969ms)
|
||||
✔ D8 URLs reject embedded credentials and unsupported protocols without echo (188.276883ms)
|
||||
✔ D9 malformed JSON diagnostics contain no content excerpt (66.696876ms)
|
||||
✔ D10 missing metadata is missing-path, not invalid-json (67.93529ms)
|
||||
✔ D10 library returns no partial entries on any invalid record (94.045756ms)
|
||||
✔ null/scalar/array metadata refuses without stack or echo (277.288274ms)
|
||||
✔ credential sibling is never opened, even when an unreadable symlink (39.956207ms)
|
||||
✔ oversized metadata refuses before parsing (57.964799ms)
|
||||
ℹ tests 69
|
||||
ℹ suites 0
|
||||
ℹ pass 69
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 2373.81712
|
||||
@@ -1,161 +1,161 @@
|
||||
✔ F1: an ordinary commit after update-ref is refused until step 8; then it commits and the queue stays at C (1331.537794ms)
|
||||
✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (1040.166322ms)
|
||||
✔ F1: an ordinary commit after update-ref is refused until step 8; then it commits and the queue stays at C (1496.47753ms)
|
||||
✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (1227.716737ms)
|
||||
ℹ 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 (1036.333657ms)
|
||||
✔ F1: a `commit -e -- path` whose guard ran before update-ref fails at its own HEAD update (1001.99318ms)
|
||||
ℹ 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 (1078.962776ms)
|
||||
✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (1029.985274ms)
|
||||
✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (1009.630991ms)
|
||||
✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1307.844422ms)
|
||||
✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (908.42089ms)
|
||||
✔ F1: a shared-index change during the procedure is not committed (1016.921274ms)
|
||||
✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (995.733784ms)
|
||||
✔ F1: a missing or a different hook refuses (701.787272ms)
|
||||
✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1169.662432ms)
|
||||
✔ F1: the canary refuses a hook that git would not run (604.167816ms)
|
||||
✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (862.581163ms)
|
||||
✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (883.291923ms)
|
||||
✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (893.86907ms)
|
||||
✔ bootstrap: the archived tests and validator run outside any repository (852.213168ms)
|
||||
✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (846.129531ms)
|
||||
✔ general: a queue write after the snapshot is not committed (1207.54959ms)
|
||||
✔ general: a snapshot whose log does not extend the base refuses (908.263093ms)
|
||||
✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (177.738465ms)
|
||||
✔ general: environment overrides, a linked worktree and usage (559.853207ms)
|
||||
✔ general: a queue path staged before the run refuses at step 1 (626.879303ms)
|
||||
✔ general: HEAD's queue tests failing in the archive refuse (803.167551ms)
|
||||
✔ genesis document serializes deterministically and replays (5.417804ms)
|
||||
✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (2.706153ms)
|
||||
✔ a tampered result, receipt or viewSha fails replay (3.234876ms)
|
||||
✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.256698ms)
|
||||
✔ add: defaults for an ordinary seat, privileged extras, refusals (4.916827ms)
|
||||
✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (7.42298ms)
|
||||
✔ matrix: release, review round, changes requested and waiting-on-jason (17.817401ms)
|
||||
✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (11.320296ms)
|
||||
✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (26.418251ms)
|
||||
✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (3.58877ms)
|
||||
✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1595.202608ms)
|
||||
✔ matrix: blocked keeps the claim and returns only to previousState (4.237519ms)
|
||||
✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.783084ms)
|
||||
✔ field edits: who may change what (5.405367ms)
|
||||
✔ set issues keeps a logged narrowing of closes (N10) (2.929429ms)
|
||||
✔ text the table shows refuses \ and <, everywhere it enters (N8) (2.096091ms)
|
||||
✔ every accepted text renders to nine cells on every row (N8) (24.693365ms)
|
||||
✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.614657ms)
|
||||
✔ times and dates must be calendar values, not just the shape (2026-10-04) (1.724399ms)
|
||||
✔ replay holds every op id to the caller's rule (N11) (5.456925ms)
|
||||
✔ note: owner, listed reviewer or privileged; empty clears (1.553456ms)
|
||||
✔ assign moves the claim with the owner; done clears it (2.816043ms)
|
||||
✔ render is byte-stable and escapes pipes (0.527543ms)
|
||||
✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.650843ms)
|
||||
✔ next: resume, then review, then start, then wait, then nothing; lowest id first (18.726734ms)
|
||||
✔ canonical args make a retry's identity independent of list order (0.342479ms)
|
||||
✔ manifests, headings and blob ids (0.45433ms)
|
||||
✔ the migration map: one queue-map block, exact keys (0.64276ms)
|
||||
✔ every call but `queue` reaches the seat CLI exactly as before A2 (672.149433ms)
|
||||
✔ no argument reaches the app entry in packages/cli, and only that call (77.444955ms)
|
||||
✔ `queue` reaches the queue CLI with the rest of the arguments (210.095797ms)
|
||||
✔ the pre-A2 fixture is the script A2 changed (0.312678ms)
|
||||
✔ an old privileged add with no gate owner still replays through loadDoc, and gets jason (8.892933ms)
|
||||
✔ a non-privileged add gets jason through the replay path and can't name a gate owner (3.365067ms)
|
||||
✔ the CLI refuses a privileged add without --gate-owner, for jason and sage, and writes nothing (285.866234ms)
|
||||
✔ the CLI add with --gate-owner carries the named owner and records a log entry (215.649063ms)
|
||||
✔ the CLI add by a non-privileged seat gets jason and can't name a gate owner (262.213362ms)
|
||||
✔ a retried sage add recorded with no gate owner answers (already recorded at rev N) and writes nothing (260.821426ms)
|
||||
✔ acquire publishes the record by link; release removes only its own lock (6.854666ms)
|
||||
✔ a kill between the temp write and the link leaves no lock (55.939657ms)
|
||||
✔ a short or failed temp write refuses and leaves no lock and no temp (3.138078ms)
|
||||
✔ a link error other than EEXIST refuses (1.76434ms)
|
||||
✔ an error after the link releases the lock: unreadable gate, failing temp stat (4.523265ms)
|
||||
✔ a release that fails on a gate path is reported, never a stack trace (P1) (5.276907ms)
|
||||
✔ a paused holder: another writer waits 10 s, then refuses naming it live (10072.141373ms)
|
||||
✔ two concurrent unlockers: the second refuses on the gate (23.173044ms)
|
||||
✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (1.816655ms)
|
||||
✔ a writer publishing during an unlock, gate first: the writer releases and refuses (1.682757ms)
|
||||
✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (1.896234ms)
|
||||
✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (3.512083ms)
|
||||
✔ the same pid and start on a different boot is mismatch (0.360087ms)
|
||||
✔ a foreign host is unknown whatever the local pid says; unlock refuses (48.27042ms)
|
||||
✔ unreadable /proc: classification is unknown and acquire refuses (0.703917ms)
|
||||
✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.220087ms)
|
||||
✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (4.406312ms)
|
||||
✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (2.295958ms)
|
||||
✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (0.932111ms)
|
||||
✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (3.308213ms)
|
||||
✔ the migration map validates and renders the golden genesis table (5.787879ms)
|
||||
✔ the marked QUEUE.md holds every row and parked item between its markers (2.211878ms)
|
||||
✔ map-check reports each kind of drift (5.877366ms)
|
||||
✔ a request posts once as the requester; a retry sends nothing (685.874397ms)
|
||||
✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (3329.01943ms)
|
||||
✔ the pre-send checks: GET user must name the requester, under the deadline (1040.62195ms)
|
||||
✔ the lead's request refuses a token for login sage (556.54807ms)
|
||||
✔ the credential file: the seat's own, 0600, no symlink, never the shared default (470.212133ms)
|
||||
✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (1485.004114ms)
|
||||
✔ a same-op retry after a kill sends nothing, even with a stale view (2261.101072ms)
|
||||
✔ a held lock at the outcome exits 3 and names what the transport said (558.825927ms)
|
||||
✔ late outcomes: after an abandon, and after a resolve with the same or another id (1499.434114ms)
|
||||
✔ resolve checks the comment: issue, markers, round, candidate and author (1536.428058ms)
|
||||
✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (486.17057ms)
|
||||
✔ validateRow checks a request round's shape, which every replayed entry must keep (347.76106ms)
|
||||
✔ request, changes, a new candidate, approval: every round pinned; no review files (1401.680358ms)
|
||||
✔ a row with no reviewers opens a round that posts nothing (833.544903ms)
|
||||
✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1149.158313ms)
|
||||
✔ semantics: v1 entries replay as before; review entries need v2 (336.448283ms)
|
||||
✔ set reviewers refuses the row's owner (2026-09-28) (255.893084ms)
|
||||
✔ the owner records no verdict, even as a listed reviewer (349.043307ms)
|
||||
✔ a request comment over the length limit is not sent (371.761724ms)
|
||||
✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (396.978823ms)
|
||||
✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (1795.737561ms)
|
||||
✔ genesis: refusals before anything is written (496.110406ms)
|
||||
✔ genesis: the map must be committed, well formed, with committed briefs and seats (655.508284ms)
|
||||
✔ genesis: markers, a stray witness, once only; a retry returns the receipt (549.119088ms)
|
||||
✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (317.370597ms)
|
||||
✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (629.929622ms)
|
||||
✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (577.934068ms)
|
||||
✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (633.221973ms)
|
||||
✔ a retried add returns the id it first allocated, after reassignment and after done (670.461078ms)
|
||||
✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (453.67716ms)
|
||||
✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (888.043954ms)
|
||||
✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (609.035102ms)
|
||||
✔ add, set reviewers and assign refuse the row's owner as a reviewer (384.942062ms)
|
||||
✔ the working-brief check: a changed working copy refuses the start and flags next (558.05983ms)
|
||||
✔ next: resume first, then nothing for an idle seat; needs a seat (283.289384ms)
|
||||
✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (580.917293ms)
|
||||
✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1029.920152ms)
|
||||
✔ a hand edit to queue.json refuses every verb, reads included (529.36113ms)
|
||||
✔ verify and render --check leave bytes and mtimes unchanged (392.789035ms)
|
||||
✔ render is byte-stable across runs and repositories (238.578069ms)
|
||||
✔ snapshot and verify --snapshot (689.489097ms)
|
||||
✔ usage errors exit 4 (534.162651ms)
|
||||
✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (220.128937ms)
|
||||
✔ a rename failure: nothing visible, temp removed (155.157612ms)
|
||||
✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (192.032261ms)
|
||||
✔ a directory fsync failure, then sync names the op (340.351152ms)
|
||||
✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (148.386967ms)
|
||||
✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (132.830175ms)
|
||||
✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (172.902304ms)
|
||||
✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (133.544513ms)
|
||||
✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (141.93407ms)
|
||||
✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (144.003935ms)
|
||||
✔ unlock prints a swapped gate's warning on stderr, the result on stdout (122.309735ms)
|
||||
✔ a view write that fails keeps the op and reports a stale view (108.058779ms)
|
||||
✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (574.209053ms)
|
||||
✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (630.590549ms)
|
||||
✔ SIGKILL after the witness, before the view: the stale refusal names the op (579.42521ms)
|
||||
✔ SIGKILL after the view, before the receipt: the retry returns the receipt (606.861953ms)
|
||||
✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (140.748903ms)
|
||||
✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (837.169137ms)
|
||||
✔ a deleted witness: refused after the locked recheck; accept-history records it absent (208.102677ms)
|
||||
✔ a header edit during a write: the op stands, the view write is skipped with a warning (137.985522ms)
|
||||
✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (127.028152ms)
|
||||
✔ rows: every row as show prints it, in list order; writes nothing; same notes and refusals as list (173.306074ms)
|
||||
✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (123.782197ms)
|
||||
✔ a writer paused before and after the witness rename: readers see a tail, then a match (135.502219ms)
|
||||
✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (491.896817ms)
|
||||
✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (461.117436ms)
|
||||
✔ the platform check refuses other filesystems (116.02771ms)
|
||||
✔ tmpfs passes only a test layer that allows it (N5) (146.043828ms)
|
||||
✔ unlock keeps a multi-line lock record on stdout (P3) (169.982853ms)
|
||||
✔ F1: step 8 with index.lock held exits 3, and ordinary commits stay refused until the printed command runs (1135.899938ms)
|
||||
✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (1121.614998ms)
|
||||
✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (1122.638757ms)
|
||||
✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1733.464503ms)
|
||||
✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (1373.642012ms)
|
||||
✔ F1: a shared-index change during the procedure is not committed (1264.376708ms)
|
||||
✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (1063.367362ms)
|
||||
✔ F1: a missing or a different hook refuses (753.018157ms)
|
||||
✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1191.889868ms)
|
||||
✔ F1: the canary refuses a hook that git would not run (587.412164ms)
|
||||
✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (910.522283ms)
|
||||
✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (880.627491ms)
|
||||
✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (904.303232ms)
|
||||
✔ bootstrap: the archived tests and validator run outside any repository (896.586735ms)
|
||||
✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (852.241092ms)
|
||||
✔ general: a queue write after the snapshot is not committed (1332.746086ms)
|
||||
✔ general: a snapshot whose log does not extend the base refuses (949.04449ms)
|
||||
✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (171.110079ms)
|
||||
✔ general: environment overrides, a linked worktree and usage (565.189629ms)
|
||||
✔ general: a queue path staged before the run refuses at step 1 (603.009987ms)
|
||||
✔ general: HEAD's queue tests failing in the archive refuse (789.659547ms)
|
||||
✔ genesis document serializes deterministically and replays (7.611931ms)
|
||||
✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (4.460693ms)
|
||||
✔ a tampered result, receipt or viewSha fails replay (3.449392ms)
|
||||
✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (1.472899ms)
|
||||
✔ add: defaults for an ordinary seat, privileged extras, refusals (6.701066ms)
|
||||
✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (8.020843ms)
|
||||
✔ matrix: release, review round, changes requested and waiting-on-jason (19.223566ms)
|
||||
✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (13.068044ms)
|
||||
✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (28.314709ms)
|
||||
✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (4.627487ms)
|
||||
✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1737.219862ms)
|
||||
✔ matrix: blocked keeps the claim and returns only to previousState (5.882222ms)
|
||||
✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (3.30683ms)
|
||||
✔ field edits: who may change what (7.639024ms)
|
||||
✔ set issues keeps a logged narrowing of closes (N10) (5.181319ms)
|
||||
✔ text the table shows refuses \ and <, everywhere it enters (N8) (2.604762ms)
|
||||
✔ every accepted text renders to nine cells on every row (N8) (27.577688ms)
|
||||
✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.624202ms)
|
||||
✔ times and dates must be calendar values, not just the shape (2026-10-04) (2.770461ms)
|
||||
✔ replay holds every op id to the caller's rule (N11) (5.374261ms)
|
||||
✔ note: owner, listed reviewer or privileged; empty clears (1.480268ms)
|
||||
✔ assign moves the claim with the owner; done clears it (2.999327ms)
|
||||
✔ render is byte-stable and escapes pipes (0.628665ms)
|
||||
✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.76703ms)
|
||||
✔ next: resume, then review, then start, then wait, then nothing; lowest id first (21.688628ms)
|
||||
✔ canonical args make a retry's identity independent of list order (0.472205ms)
|
||||
✔ manifests, headings and blob ids (0.486529ms)
|
||||
✔ the migration map: one queue-map block, exact keys (0.649184ms)
|
||||
✔ every call but `queue` reaches the seat CLI exactly as before A2 (740.170793ms)
|
||||
✔ no argument reaches the app entry in packages/cli, and only that call (84.997996ms)
|
||||
✔ `queue` reaches the queue CLI with the rest of the arguments (252.88095ms)
|
||||
✔ the pre-A2 fixture is the script A2 changed (0.335061ms)
|
||||
✔ an old privileged add with no gate owner still replays through loadDoc, and gets jason (10.311718ms)
|
||||
✔ a non-privileged add gets jason through the replay path and can't name a gate owner (3.11548ms)
|
||||
✔ the CLI refuses a privileged add without --gate-owner, for jason and sage, and writes nothing (330.238012ms)
|
||||
✔ the CLI add with --gate-owner carries the named owner and records a log entry (230.881296ms)
|
||||
✔ the CLI add by a non-privileged seat gets jason and can't name a gate owner (318.047493ms)
|
||||
✔ a retried sage add recorded with no gate owner answers (already recorded at rev N) and writes nothing (260.47085ms)
|
||||
✔ acquire publishes the record by link; release removes only its own lock (8.297424ms)
|
||||
✔ a kill between the temp write and the link leaves no lock (66.307215ms)
|
||||
✔ a short or failed temp write refuses and leaves no lock and no temp (3.469678ms)
|
||||
✔ a link error other than EEXIST refuses (1.897653ms)
|
||||
✔ an error after the link releases the lock: unreadable gate, failing temp stat (3.614227ms)
|
||||
✔ a release that fails on a gate path is reported, never a stack trace (P1) (5.079665ms)
|
||||
✔ a paused holder: another writer waits 10 s, then refuses naming it live (10077.60966ms)
|
||||
✔ two concurrent unlockers: the second refuses on the gate (43.312507ms)
|
||||
✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (2.635508ms)
|
||||
✔ a writer publishing during an unlock, gate first: the writer releases and refuses (2.609199ms)
|
||||
✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (2.987541ms)
|
||||
✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (5.738522ms)
|
||||
✔ the same pid and start on a different boot is mismatch (0.977877ms)
|
||||
✔ a foreign host is unknown whatever the local pid says; unlock refuses (76.863102ms)
|
||||
✔ unreadable /proc: classification is unknown and acquire refuses (1.233134ms)
|
||||
✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.358795ms)
|
||||
✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (5.915416ms)
|
||||
✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (4.093459ms)
|
||||
✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (1.762147ms)
|
||||
✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (5.746018ms)
|
||||
✔ the migration map validates and renders the golden genesis table (6.105482ms)
|
||||
✔ the marked QUEUE.md holds every row and parked item between its markers (2.326147ms)
|
||||
✔ map-check reports each kind of drift (6.571595ms)
|
||||
✔ a request posts once as the requester; a retry sends nothing (763.635492ms)
|
||||
✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (3553.745865ms)
|
||||
✔ the pre-send checks: GET user must name the requester, under the deadline (1053.025721ms)
|
||||
✔ the lead's request refuses a token for login sage (590.403532ms)
|
||||
✔ the credential file: the seat's own, 0600, no symlink, never the shared default (526.827642ms)
|
||||
✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (1826.44091ms)
|
||||
✔ a same-op retry after a kill sends nothing, even with a stale view (3134.724575ms)
|
||||
✔ a held lock at the outcome exits 3 and names what the transport said (582.302816ms)
|
||||
✔ late outcomes: after an abandon, and after a resolve with the same or another id (1580.26454ms)
|
||||
✔ resolve checks the comment: issue, markers, round, candidate and author (1536.143353ms)
|
||||
✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (525.926722ms)
|
||||
✔ validateRow checks a request round's shape, which every replayed entry must keep (351.414357ms)
|
||||
✔ request, changes, a new candidate, approval: every round pinned; no review files (1425.991363ms)
|
||||
✔ a row with no reviewers opens a round that posts nothing (914.764647ms)
|
||||
✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1159.429979ms)
|
||||
✔ semantics: v1 entries replay as before; review entries need v2 (367.399877ms)
|
||||
✔ set reviewers refuses the row's owner (2026-09-28) (260.155857ms)
|
||||
✔ the owner records no verdict, even as a listed reviewer (411.042958ms)
|
||||
✔ a request comment over the length limit is not sent (403.006139ms)
|
||||
✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (414.821887ms)
|
||||
✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (1804.367513ms)
|
||||
✔ genesis: refusals before anything is written (550.392177ms)
|
||||
✔ genesis: the map must be committed, well formed, with committed briefs and seats (721.438887ms)
|
||||
✔ genesis: markers, a stray witness, once only; a retry returns the receipt (582.721601ms)
|
||||
✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (399.660856ms)
|
||||
✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (740.544923ms)
|
||||
✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (550.311942ms)
|
||||
✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (683.527066ms)
|
||||
✔ a retried add returns the id it first allocated, after reassignment and after done (664.579893ms)
|
||||
✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (473.76779ms)
|
||||
✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (961.213855ms)
|
||||
✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (706.363062ms)
|
||||
✔ add, set reviewers and assign refuse the row's owner as a reviewer (424.817452ms)
|
||||
✔ the working-brief check: a changed working copy refuses the start and flags next (720.513159ms)
|
||||
✔ next: resume first, then nothing for an idle seat; needs a seat (444.053385ms)
|
||||
✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (817.015002ms)
|
||||
✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1502.928174ms)
|
||||
✔ a hand edit to queue.json refuses every verb, reads included (568.274644ms)
|
||||
✔ verify and render --check leave bytes and mtimes unchanged (401.225496ms)
|
||||
✔ render is byte-stable across runs and repositories (265.861202ms)
|
||||
✔ snapshot and verify --snapshot (722.643012ms)
|
||||
✔ usage errors exit 4 (559.835093ms)
|
||||
✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (237.793981ms)
|
||||
✔ a rename failure: nothing visible, temp removed (170.824913ms)
|
||||
✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (194.541743ms)
|
||||
✔ a directory fsync failure, then sync names the op (381.617845ms)
|
||||
✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (167.358203ms)
|
||||
✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (157.236938ms)
|
||||
✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (163.964324ms)
|
||||
✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (158.066179ms)
|
||||
✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (151.155482ms)
|
||||
✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (171.722557ms)
|
||||
✔ unlock prints a swapped gate's warning on stderr, the result on stdout (147.121849ms)
|
||||
✔ a view write that fails keeps the op and reports a stale view (138.89759ms)
|
||||
✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (638.190568ms)
|
||||
✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (651.142918ms)
|
||||
✔ SIGKILL after the witness, before the view: the stale refusal names the op (599.592812ms)
|
||||
✔ SIGKILL after the view, before the receipt: the retry returns the receipt (615.32047ms)
|
||||
✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (154.634945ms)
|
||||
✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (893.011123ms)
|
||||
✔ a deleted witness: refused after the locked recheck; accept-history records it absent (223.240305ms)
|
||||
✔ a header edit during a write: the op stands, the view write is skipped with a warning (154.348047ms)
|
||||
✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (142.613262ms)
|
||||
✔ rows: every row as show prints it, in list order; writes nothing; same notes and refusals as list (202.883243ms)
|
||||
✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (140.701709ms)
|
||||
✔ a writer paused before and after the witness rename: readers see a tail, then a match (163.632937ms)
|
||||
✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (519.501764ms)
|
||||
✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (470.642471ms)
|
||||
✔ the platform check refuses other filesystems (210.7952ms)
|
||||
✔ tmpfs passes only a test layer that allows it (N5) (263.45671ms)
|
||||
✔ unlock keeps a multi-line lock record on stdout (P3) (254.191079ms)
|
||||
ℹ tests 156
|
||||
ℹ suites 0
|
||||
ℹ pass 156
|
||||
@@ -163,4 +163,4 @@
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 21929.78614
|
||||
ℹ duration_ms 24009.662391
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
✔ isRunId accepts the run id shape and nothing else (1.091896ms)
|
||||
✔ a missing data root or runs directory lists nothing (1.687109ms)
|
||||
✔ runs as a regular file lists nothing, as before (0.59439ms)
|
||||
✔ listRunIds keeps r- names only, sorted oldest first (0.750374ms)
|
||||
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.204771ms)
|
||||
✔ readRunRecord returns documents and artifacts in directory order (1.106136ms)
|
||||
✔ readRunRecord is null for a missing run, a dangling link or a file (0.746045ms)
|
||||
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.763742ms)
|
||||
✔ readRunDocument reads only the three run documents (0.603525ms)
|
||||
✔ a link inside the data root is followed (1.011542ms)
|
||||
✔ a data root that is itself a link is trusted as configured (0.56073ms)
|
||||
✔ a run directory linked out of the data root is never read (0.642939ms)
|
||||
✔ a document linked out of the data root reads as null (1.003223ms)
|
||||
✔ a runs directory linked out of the data root refuses (0.644778ms)
|
||||
✔ a relative link that climbs out of the data root refuses (0.461157ms)
|
||||
✔ a sibling whose name starts with the data root's name is outside it (0.57179ms)
|
||||
✔ a link loop refuses instead of reading as missing (0.672221ms)
|
||||
✔ an unreadable runs directory refuses instead of listing nothing (0.500923ms)
|
||||
✔ an unreadable run directory refuses instead of reading as missing (0.564129ms)
|
||||
✔ a link to the data root's parent is outside it (0.442149ms)
|
||||
✔ listRunIds sorts whatever order the directory returns (0.85649ms)
|
||||
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.486149ms)
|
||||
✔ the readers write nothing (2.007656ms)
|
||||
✔ no pointer is null (2.010778ms)
|
||||
✔ the pointer release.sh writes reads back (1.539761ms)
|
||||
✔ a pointer that isn't the version 1 shape refuses with exit 2 (13.184152ms)
|
||||
✔ a pointer that is a directory refuses with exit 4 (0.675583ms)
|
||||
✔ a state file linked out of the data root refuses (1.036081ms)
|
||||
✔ a state directory linked out of the data root refuses (0.581589ms)
|
||||
✔ a state directory linked out to nothing reads as no release and an empty log (0.483751ms)
|
||||
✔ a missing log is empty (0.421512ms)
|
||||
✔ the log reads oldest first and counts malformed lines (0.978331ms)
|
||||
✔ last must be a positive integer (0.468547ms)
|
||||
✔ the state readers write nothing (0.853362ms)
|
||||
✔ list prints each run in the established format (77.843774ms)
|
||||
✔ list prints a damaged result.json as unknown and keeps listing the runs after it (65.46797ms)
|
||||
✔ list prints each result field that isn't a string as unknown or - (76.113316ms)
|
||||
✔ show prints the run in the established format (70.292684ms)
|
||||
✔ show of a missing run and an invalid id exit 4 as before (103.287626ms)
|
||||
✔ list and show refuse a runs directory linked out of the data root (133.438875ms)
|
||||
✔ show refuses a run linked out of the data root; list reports it unknown (126.427652ms)
|
||||
✔ list shows an r- name that isn't a valid run id, as before (65.438793ms)
|
||||
✔ show refuses an invalid id before reading the config (38.602248ms)
|
||||
ℹ tests 43
|
||||
ℹ suites 0
|
||||
ℹ pass 43
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 813.638541
|
||||
@@ -0,0 +1,131 @@
|
||||
✔ a first launch clones the root at its branch, with the root's path as origin (63.769868ms)
|
||||
✔ the clone is not at its place until git clone has finished (31.403833ms)
|
||||
✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (88.75782ms)
|
||||
✔ a clone ahead of the root keeps its commits and counts as current (46.362212ms)
|
||||
▶ a dirty clone, one on another branch and one diverged are kept as they are
|
||||
✔ a modified file (35.32994ms)
|
||||
✔ an untracked file (35.448217ms)
|
||||
✔ a staged file (41.695124ms)
|
||||
✔ another branch (38.705976ms)
|
||||
✔ a detached HEAD (37.873015ms)
|
||||
✔ a diverged branch (56.138589ms)
|
||||
✔ a dirty clone, one on another branch and one diverged are kept as they are (246.653272ms)
|
||||
▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it
|
||||
✔ user.name (32.996364ms)
|
||||
✔ user.email (32.131039ms)
|
||||
✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (65.668411ms)
|
||||
✔ a commit git can't read after the fast-forward is null (64.458635ms)
|
||||
✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (24.09052ms)
|
||||
▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it
|
||||
✔ core.fsmonitor (51.847076ms)
|
||||
✔ core.hooksPath (44.474961ms)
|
||||
✔ a hook file (41.415495ms)
|
||||
✔ include.path (46.368927ms)
|
||||
✔ includeIf (44.241379ms)
|
||||
✔ a filter driver (39.345704ms)
|
||||
✔ origin moved (45.291106ms)
|
||||
✔ a second origin url (54.529326ms)
|
||||
✔ the origin url twice, both the root (44.856281ms)
|
||||
✔ an ext:: origin (40.013896ms)
|
||||
✔ a branch tracking another remote (41.507469ms)
|
||||
✔ a branch merging a tag (37.466146ms)
|
||||
✔ a fetch line onto local branches (39.739455ms)
|
||||
✔ no origin (38.29448ms)
|
||||
✔ a config file over the size cap (38.977612ms)
|
||||
✔ config a symlink to /dev/zero (38.272461ms)
|
||||
✔ the clone a symlink to the bait (40.406568ms)
|
||||
✔ the clone a file (55.234822ms)
|
||||
✔ the clone a plain directory (61.272986ms)
|
||||
✔ .git a gitdir: file (65.819806ms)
|
||||
✔ .git a symlink (56.635565ms)
|
||||
✔ .git/logs a symlink (59.768446ms)
|
||||
✔ .git/refs/heads a symlink (58.930459ms)
|
||||
✔ .git/objects a symlink (77.260725ms)
|
||||
✔ a reflog hard-linked to a file outside (59.330356ms)
|
||||
✔ HEAD a fifo (66.724927ms)
|
||||
✔ a commondir file (62.261936ms)
|
||||
✔ an alternates file (53.314558ms)
|
||||
✔ a gitlink staged, no .gitmodules (67.45618ms)
|
||||
✔ a gitlink and .gitmodules staged (70.56417ms)
|
||||
✔ a gitlink to a repository with its own gitlink (86.487607ms)
|
||||
✔ a gitlink and .gitmodules committed (70.300461ms)
|
||||
✔ a gitlink and submodule.active in config (81.412152ms)
|
||||
✔ a .git/modules with no gitlink (62.610968ms)
|
||||
✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1849.504102ms)
|
||||
▶ a root that can't be cloned refuses with the project id and leaves nothing behind
|
||||
✔ missing (18.627233ms)
|
||||
✔ not a repository (17.689073ms)
|
||||
✔ a plain directory inside another repository (23.413602ms)
|
||||
✔ unreadable (15.505386ms)
|
||||
✔ on no branch (21.025395ms)
|
||||
✔ missing an object (27.456998ms)
|
||||
✔ a root that can't be cloned refuses with the project id and leaves nothing behind (125.229229ms)
|
||||
✔ a clone whose root is on no branch, or gone, is kept and named (57.984927ms)
|
||||
✔ no credential or forge remote of the root reaches a clone (87.591549ms)
|
||||
✔ every project in the business file gets its own clone at its own branch (60.987023ms)
|
||||
✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (521.793791ms)
|
||||
✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1025.052854ms)
|
||||
✔ a timeout signals git's process group, so a process git started dies with it (523.070036ms)
|
||||
✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (521.557742ms)
|
||||
✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (2.976944ms)
|
||||
✔ sandboxProblem names a missing bwrap and a bwrap that fails (12.784387ms)
|
||||
✔ every bind says why; the bundle and session file stay read-only over the run directory (2.896538ms)
|
||||
✔ the host's git socket is bound only when given, read-only and before the writable binds (1.216442ms)
|
||||
✔ the git identity is the instance's, at <instance>@<business>.mosaic.invalid, and sandboxArgv refuses without it (1.936674ms)
|
||||
✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (2072.35165ms)
|
||||
✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (3368.723221ms)
|
||||
✔ resolveSeat: by name under --repo resolves the repo layout (2.119775ms)
|
||||
✔ resolveSeat: by path resolves the fleet layout (0.598435ms)
|
||||
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (1.153729ms)
|
||||
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.808248ms)
|
||||
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (1.005897ms)
|
||||
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (2.407623ms)
|
||||
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.76825ms)
|
||||
✔ CLI launch: registers, execs the fake launch script, and passes args through (48.686743ms)
|
||||
✔ CLI launch: --harness lands in the record (44.168195ms)
|
||||
✔ CLI launch: the launch script's own exit code passes through (37.876554ms)
|
||||
✔ CLI launch: relaunching a seat rewrites the one registration record (67.756593ms)
|
||||
✔ CLI launch: omitting --task records an empty string, not null (30.692715ms)
|
||||
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (85.349541ms)
|
||||
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (156.763737ms)
|
||||
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.513868ms)
|
||||
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.724041ms)
|
||||
✔ 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.911201ms)
|
||||
✔ 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 (1.006267ms)
|
||||
✔ 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 (307.398387ms)
|
||||
✔ family: exactly one launch.max key in the model name, else null (1.16343ms)
|
||||
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.954116ms)
|
||||
✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.416408ms)
|
||||
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (2.847866ms)
|
||||
✔ session file and launch log: 0600, the session file written once (1.895304ms)
|
||||
✔ endReason maps the runner's exit codes; a signal is killed (0.192075ms)
|
||||
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.843919ms)
|
||||
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (1.43934ms)
|
||||
✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (251.718345ms)
|
||||
✔ workRef and validBranch take only what a ref name can hold (1.941509ms)
|
||||
✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (151.937116ms)
|
||||
✔ a rewritten branch refuses non-fast-forward and the work ref stays (100.336674ms)
|
||||
✔ a branch with no history in common with main refuses unrelated-history (57.595557ms)
|
||||
✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (48.955126ms)
|
||||
✔ main is fetched from the root again before each push, so a clone of a newer root pushes (121.198846ms)
|
||||
✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (80.050202ms)
|
||||
✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (83.935749ms)
|
||||
✔ only a v2 bundle with the named branch is read (61.374243ms)
|
||||
✔ a branch named main, or named for another task, moves only this task's work ref (98.806364ms)
|
||||
✔ a record that throws moves the work ref back, or removes a new one (129.402295ms)
|
||||
✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (391.898376ms)
|
||||
✔ a root that can't be read refuses root-unreadable (47.977421ms)
|
||||
✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2236.433043ms)
|
||||
✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1047.067278ms)
|
||||
✔ a working repository path that isn't a directory refuses (45.656046ms)
|
||||
✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (206.429479ms)
|
||||
✔ review of a project with no working repository refuses unknown-commit (24.514528ms)
|
||||
✔ the runner's git tools hold the host's limits and branch rule (1.828812ms)
|
||||
ℹ tests 119
|
||||
ℹ suites 0
|
||||
ℹ pass 119
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 5557.284487
|
||||
@@ -0,0 +1,100 @@
|
||||
✔ the boot config is checked before anything starts (17.170215ms)
|
||||
✔ a business with no tracker entry refuses task verbs (15.808751ms)
|
||||
✔ credential.expiring and .expired are recorded once per instance (31.067729ms)
|
||||
✔ a token file that changes on disk records credential.changed (16.57654ms)
|
||||
✔ autostart polls, reconciles and retries a startup the tracker was down for (38.400512ms)
|
||||
✔ a refusal a restart must clear is not retried by the poll (10.703005ms)
|
||||
✔ a poll that fires while two are queued is dropped (14.58789ms)
|
||||
✔ close waits for a running verb and refuses one that has not started (48.182115ms)
|
||||
✔ the bundled Vikunja is the pinned upstream image the runbook names (0.907841ms)
|
||||
✔ every published port is on 127.0.0.1, and no secret is in the file (0.301652ms)
|
||||
✔ the committed table loads frozen; its verbs are the broker task verbs (1.913348ms)
|
||||
✔ a write names a verb the table has as its own key, not one it inherits (0.798938ms)
|
||||
✔ the schema refuses any other shape (3.679019ms)
|
||||
✔ the Field table in the slice 1 plan names the verbs each field is written by (1.162217ms)
|
||||
✔ each verb refuses, asks for a decision and writes as the table and the role files say (212.29644ms)
|
||||
✔ the list holds the PRD's requirement ids, in its order, at its version (2.922439ms)
|
||||
✔ the committed list loads frozen (0.617951ms)
|
||||
✔ the schema refuses any other shape (2.393511ms)
|
||||
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (106.737954ms)
|
||||
✔ the project list holds recorded project objects in the recorded list envelope (12.871488ms)
|
||||
✔ the recorded task bodies pass the checks S3 applies to every read (0.960998ms)
|
||||
✔ the client works against the fake over real HTTP with the platform fetch (125.416677ms)
|
||||
✔ a correct install starts, and the first reconcile records tasks that already exist (30.547773ms)
|
||||
✔ verbs refuse while a business is starting and after startup refused it (17.988323ms)
|
||||
✔ startup refuses a token that can do more than its role needs (45.329815ms)
|
||||
✔ startup refuses an unsupported version and flags an untested one (35.458013ms)
|
||||
✔ startup refuses a board that the runbook did not install (44.395887ms)
|
||||
✔ startup refuses a project the sync bot cannot read (10.492462ms)
|
||||
✔ startup refuses a configured label the pm bot cannot see (12.749938ms)
|
||||
✔ startup refuses an expired credential and a missing sync credential (22.653094ms)
|
||||
✔ an unreachable tracker refuses with tracker-unavailable (10.698885ms)
|
||||
✔ an edit in the UI is recorded once, with the fields that changed (87.064601ms)
|
||||
✔ a move between open buckets is seen on the board, though updated does not change (45.422309ms)
|
||||
✔ a person's comment is counted and a bot's is not (78.008767ms)
|
||||
✔ the hourly reconcile catches a comment through comment_count (89.993492ms)
|
||||
✔ a task closed in the UI leaves the open view with its done bucket (116.538045ms)
|
||||
✔ a task that leaves the board is recorded as deleted, moved or out of reach (67.423283ms)
|
||||
✔ a poll that read before a verb wrote does not overwrite the verb (34.890431ms)
|
||||
✔ a tracker fault during a tick is reported and the next tick catches up (35.910201ms)
|
||||
✔ a malformed answer refuses the tick with tracker-shape (13.845427ms)
|
||||
✔ no token value reaches the database, the log or a refusal (47.002521ms)
|
||||
✔ the first look at a task counts only comments inside the window (35.97766ms)
|
||||
✔ with descendants off the adapter reads no project list and no child, as in v0 (51.489089ms)
|
||||
✔ a three-level tree is read per project, and a project outside it is not (25.60069ms)
|
||||
✔ a watch-only child keeps the business ready, and a failed child read refuses as transient (28.987591ms)
|
||||
✔ a root without the installed buckets refuses with descendants on (11.221657ms)
|
||||
✔ the project list refuses without read_all, and skips archived projects and saved filters (28.952098ms)
|
||||
✔ descendants must be true or false (10.907308ms)
|
||||
✔ a move inside the tree is a moved tombstone plus a snapshot under the new ref (71.395011ms)
|
||||
✔ a child made after startup joins the tree at the next reconcile (63.256119ms)
|
||||
✔ a child that leaves the tree has its tasks recorded missing, and the ledger keeps their history (62.501206ms)
|
||||
✔ a child deleted or hidden between reconciles drops out at the next tick, and the root still refuses (82.600743ms)
|
||||
✔ a reconcile that fails keeps the previous tree (32.643836ms)
|
||||
✔ a closed task the cursor missed takes its own project's done bucket (34.667086ms)
|
||||
✔ closed tasks read at startup take their own project's done bucket (9.530388ms)
|
||||
✔ a closed task the cursor returns takes its own project's done bucket (33.849074ms)
|
||||
✔ a task moved out of a child that leaves the tree at the same time is moved, not no-access (33.727959ms)
|
||||
✔ a task in a bucket outside the five keeps its raw bucket id (32.197568ms)
|
||||
✔ bucket 0 on a watch-only task never becomes a bucket or a state (79.67026ms)
|
||||
✔ a child whose kanban view is deleted still refuses the tick, and the reconcile reads it watch-only (56.729074ms)
|
||||
✔ a child dropped at a tick comes back at the next reconcile once the bot reads it again (33.950115ms)
|
||||
✔ task.create needs a recorded human request and a requirement id (43.94098ms)
|
||||
✔ task.create takes only an approved requirement id (27.884473ms)
|
||||
✔ task.create cites the request a received message carries, not the message's id (23.35944ms)
|
||||
✔ only labels named in the business file can be written (32.647975ms)
|
||||
✔ task.schedule sets and clears a due date and relations (42.642424ms)
|
||||
✔ assign and reassign move the role bots and record task.assigned (70.979662ms)
|
||||
✔ task.update.assigned is for the assignee and records task.state (60.857728ms)
|
||||
✔ a wrong expected digest records task.conflict and writes nothing (21.282355ms)
|
||||
✔ a cross-role verb needs a resolved decision, used once (22.729928ms)
|
||||
✔ task.close needs a verdict; after it every verb refuses with task-done (24.950609ms)
|
||||
✔ a task with pushed work closes only citing an approve verdict for its latest push, and a refusal writes nothing (29.491395ms)
|
||||
✔ a lost answer is settled by a re-read and never retried (27.852475ms)
|
||||
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (36.071858ms)
|
||||
✔ a task the sync bot cannot read refuses and records nothing (16.616728ms)
|
||||
✔ verbs and polls for one business run one at a time (89.429764ms)
|
||||
✔ a due date with milliseconds is written to the second (68.708738ms)
|
||||
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (38.05984ms)
|
||||
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (14.673612ms)
|
||||
✔ a create whose final read fails succeeds and records task.created (34.339039ms)
|
||||
✔ an edit between the last write and the final read shows as external on the next poll (36.417836ms)
|
||||
✔ task.created is recorded when a later label write fails (12.6706ms)
|
||||
✔ every verb acts in an installed child, on the child's own board (45.973377ms)
|
||||
✔ task.create refuses a project outside the tree, a watch-only one and one that is not an id (24.610731ms)
|
||||
✔ with descendants off, task.create takes only the root project (12.468328ms)
|
||||
✔ a verb on a watch-only task, or a relation to one, refuses with project-not-installed (16.722616ms)
|
||||
✔ a related task is read before authority: in another project than its ref names, task-moved; missing, task-not-found (23.852659ms)
|
||||
✔ a task moved to another project of the tree refuses its old ref with task-moved and takes its new one (15.611255ms)
|
||||
✔ a verb and a create in the child record the final read of the child's board (15.493776ms)
|
||||
✔ a create in the child whose final read fails records the child's todo bucket (35.99464ms)
|
||||
✔ a lost answer in the child is settled by a re-read of the child's board (19.918302ms)
|
||||
✔ every related task is read before authority, not only the first (15.182779ms)
|
||||
ℹ tests 92
|
||||
ℹ suites 0
|
||||
ℹ pass 92
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1133.061435
|
||||
@@ -1,7 +1,39 @@
|
||||
On branch refactor
|
||||
Your branch is up to date with 'origin/refactor'.
|
||||
Note: switching to '02fe2d9d552212049bef5efbca981945bbdb84f5'.
|
||||
|
||||
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 '02fe2d9d552212049bef5efbca981945bbdb84f5'.
|
||||
|
||||
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: 3879204 Killed "$@" > /dev/null 2>&1
|
||||
scripts/test-extension-package.sh: line 14: 310263 Killed "$@" > /dev/null 2>&1
|
||||
OK forced interruption kills the replacing process
|
||||
OK next invocation recovers old consistent installation
|
||||
OK interrupted replacement rolled back
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
✔ the Board: held roles with no machine sessions, a claim with no address, the toggle kept across a reload, and Message addressed to the role (6985.003033ms)
|
||||
✔ a redirect answer on a Board read, a bus read, a conversation read, a board POST, a module read and a send signs the page out: one line, nothing stale, no further reads (7708.33956ms)
|
||||
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5175.366322ms)
|
||||
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
|
||||
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (6193.414165ms)
|
||||
✔ the README says a refused field table names the file or the key at fault, never a path (1.673647ms)
|
||||
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (8662.48321ms)
|
||||
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (215.397194ms)
|
||||
✔ decisions route serves the roles and recent lists from the reader view (333.826989ms)
|
||||
✔ credentials route serves the broker credential status from the reader view (506.006884ms)
|
||||
✔ replies route serves the replies to one message from the reader view (848.055338ms)
|
||||
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (714.469555ms)
|
||||
✔ bus refusals map to statuses and never carry a path or a stack (125.074818ms)
|
||||
✔ humanCall speaks the human transport protocol without blocking the server (3407.967339ms)
|
||||
✔ busReader takes --business, else the live bus host, else refuses (972.937004ms)
|
||||
✔ humanCall kills a transport that runs past its timeout (549.116116ms)
|
||||
✔ serve refuses a --business value that is not a business id (1293.008696ms)
|
||||
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4570.795251ms)
|
||||
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (4144.41758ms)
|
||||
✔ conversation view: a newer session with no readable history keeps the marker (1981.511009ms)
|
||||
✔ conversation view: seats without history say so and offer no reply (1626.256788ms)
|
||||
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4081.93497ms)
|
||||
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56997.756827ms)
|
||||
✔ messages HTTP read pages and preserves metadata; bad arguments refuse before a read; other reads unchanged (228.829883ms)
|
||||
✔ Console Messages lists late unsolicited messages on polling with sender class time read state and trail; nav count includes beyond the page; reads never deliver (7268.096033ms)
|
||||
✔ Messages nav count polls on Tasks and Board and clears on a Board refusal; messages 403 refuses local pages (3679.733181ms)
|
||||
✔ copy writes the Console paths at the full id, read-only, and leaves an existing copy alone (6062.306208ms)
|
||||
✔ copy refuses a commit off refactor, an unknown one, a worktree and a bad copies directory (3580.944581ms)
|
||||
✔ copy writes nothing when an archived path is missing at the commit (478.666056ms)
|
||||
✔ a copy that loses the rename race leaves no read-only temp directory behind (1046.170178ms)
|
||||
✔ a copy whose rename fails with no target refuses, writes no copy and leaves no temp directory (1174.387673ms)
|
||||
✔ a copy that meets a target holding files says exists: and moves nothing into it (529.760593ms)
|
||||
✔ a copy that meets an empty target directory replaces it by rename, not by moving into it (552.900422ms)
|
||||
✔ serve runs from a copy: /healthz 200, the config check, roles and RELEASE from the copy, the queue from --checkout (1064.480153ms)
|
||||
✔ serve refuses a --checkout that is not an absolute directory path (283.703702ms)
|
||||
✔ consoleReads runs the queue read under queueRoot and reads RELEASE under root (63.680453ms)
|
||||
✔ preview options are all three or none, and each is checked (12.864659ms)
|
||||
✔ the CLI refuses a partial or invalid preview flag set at start (4658.793449ms)
|
||||
✔ the CLI with all three flags serves the preview mode (728.52212ms)
|
||||
✔ a request for the public name needs the header equal to the user id, else 401 and nothing read (101.224566ms)
|
||||
✔ the header name matches without regard to case, and the value compare is exact (40.683438ms)
|
||||
✔ the public name with the header reads the bus views, and the Host and Origin are checked first (145.103088ms)
|
||||
✔ in preview mode every write answers 403 read-only, from the public name and loopback, and reaches nothing (43.621492ms)
|
||||
✔ loopback requests are unchanged in preview mode, and the bus views work with the board down (27.274956ms)
|
||||
✔ a proxy that rewrites Host to the loopback port gets 403 on its first request, whatever the user (131.221107ms)
|
||||
✔ a repeated Host is 400 before any other check, in preview mode and without it (35.568195ms)
|
||||
✔ without the preview flags the public name is refused and writes still reach the board (16.551299ms)
|
||||
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.634887ms)
|
||||
✔ credential state from the date alone, as the bus states it (0.312237ms)
|
||||
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (4372.72113ms)
|
||||
✔ queue: a refused read fails closed with the store's text, redacted (1297.159138ms)
|
||||
✔ queue: a store that fails to load, or a child that dies, gives a fixed message with no path or stack (3560.161565ms)
|
||||
✔ business: names, vars on the allowlist, authority, credential metadata only (70.946169ms)
|
||||
✔ business: missing or invalid file refuses with the module's text, redacted (38.659373ms)
|
||||
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (51.110476ms)
|
||||
✔ settings: release, board, loopback address, notifier binding name only (367.648438ms)
|
||||
✔ task fields: the committed table with no reads configured, GET only; a bad table refuses with no path (57.850166ms)
|
||||
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4231.936744ms)
|
||||
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23794.205964ms)
|
||||
✔ the send box: held roles, the draft and caret across a refresh, each receipt, one POST per Send, and the trail link; the board Reply on a 4xx and a 5xx (10061.748553ms)
|
||||
✔ a sent receipt lists its replies as the page refreshes, oldest first and capped, and marks none delivered or read (12218.365979ms)
|
||||
✔ startServer takes send only as a function and only with the preview mode (159.457874ms)
|
||||
✔ the CLI refuses --send without the preview flags, and without a configured bus (2174.338182ms)
|
||||
✔ the CLI with --send and a configured bus sends through the human transport and logs one line (710.443728ms)
|
||||
✔ without --send the route is read-only like every other write, and config says no send (88.165533ms)
|
||||
✔ with --send every other write is still 403 read-only, and nothing reaches the board or bus (28.59486ms)
|
||||
✔ config says send only on the public name with --send on (53.59573ms)
|
||||
✔ a send without the signed-in user is 401 and is neither sent nor logged, loopback included (18.146058ms)
|
||||
✔ each request check refuses before anything is sent, and logs the refusal without the body (54.793763ms)
|
||||
✔ a send passes exactly to, body and in_reply_to, answers the id, and logs it (47.502559ms)
|
||||
✔ a crafted role name cannot forge a log line (17.215852ms)
|
||||
✔ a bus refusal is not sent, with its status; an outcome the bus could not confirm is unknown and never retried (34.457994ms)
|
||||
✔ through the human transport the request is exactly the business, message.send and the keys (451.633273ms)
|
||||
✔ against a real broker: the message is written once as a human send, and its refusals map (239.073428ms)
|
||||
✔ loopback host and board origin fail closed (13.836402ms)
|
||||
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (343.947755ms)
|
||||
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (231.987826ms)
|
||||
✔ unreachable board reports URL; CLI rejects unsupported options (1864.699804ms)
|
||||
✔ render prints the unit without the preview flags (120.884733ms)
|
||||
✔ render carries every serve flag, the preview set included (97.089047ms)
|
||||
✔ a partial preview set is refused (159.915844ms)
|
||||
✔ a value the unit cannot carry is refused, for every flag (2915.779928ms)
|
||||
✔ the rendered unit names none of the human proof agent markers and sets no agent variable (116.094806ms)
|
||||
✔ install into --dir with --no-reload writes the file whole, then reports unchanged (255.613433ms)
|
||||
✔ install without --no-reload runs daemon-reload and nothing that starts the unit (157.947565ms)
|
||||
✔ uninstall refuses while the unit is running, with or without --no-reload, and removes it once it is not (419.384707ms)
|
||||
✔ status prints the unit state and GET /healthz on the installed port (383.942219ms)
|
||||
✔ usage: an unknown verb or flag, and start or enable, exit 4 (172.349002ms)
|
||||
✔ without --send, render is the template with its placeholders filled, byte for byte (105.106538ms)
|
||||
✔ render --send with the preview set ends ExecStart with --send and changes nothing else (283.908685ms)
|
||||
✔ --send without the whole preview set is refused and writes nothing (769.621301ms)
|
||||
✔ install --send into --dir with --no-reload writes the sending unit, and status says whether it sends (553.748208ms)
|
||||
✔ uninstall and status take no --send (79.138019ms)
|
||||
✔ install refuses while the unit is running, in any state but inactive or failed, and changes nothing (370.48255ms)
|
||||
✔ when the state read fails, install and uninstall refuse with one line and change nothing (79.068085ms)
|
||||
✔ install needs --copy naming a copy that exists, and refuses outside the canonical checkout without --allow-worktree (72.213691ms)
|
||||
✔ install refuses unless the bus unit runs from the same copy, naming both paths, and changes nothing (292.75965ms)
|
||||
✔ render --copy runs the copy and reads the queue in the checkout; without --copy the unit is unchanged (23.797523ms)
|
||||
✔ the uid header takes only an Authentik uid; the username header still takes a username (516.00385ms)
|
||||
✔ a preview value cannot start with - or . (59.603778ms)
|
||||
✔ status names the copy and whether refactor has moved past it, and says when the unit runs from no copy (537.695428ms)
|
||||
✔ tokens.css and icons.svg are byte copies of docs/design (4.281248ms)
|
||||
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (6.068024ms)
|
||||
✔ shipped strings are unchanged, and the web keeps its write boundary (4.843601ms)
|
||||
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (9095.449438ms)
|
||||
✔ five states on Board, Inbox, Tasks, Agents and Trail (2471.618593ms)
|
||||
✔ Queue, Business and Settings in their five states, with no move button (6315.464745ms)
|
||||
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1573.516456ms)
|
||||
✔ the server log names no config path when the system config refuses (321.220642ms)
|
||||
ℹ tests 108
|
||||
ℹ suites 0
|
||||
ℹ pass 108
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 57600.350583
|
||||
Reference in New Issue
Block a user