researcher: row 80 round 1 packet (#1574)
Stranded delivery list on the bus agents view. Built by Sage's Haiku
subagent (decision 129), rebased onto 57a2ddc5. Phase B, three runs:
the first failed the new test (node:sqlite null-prototype rows, fixed
by mapping to plain objects), the second stopped at cli on a missing
node_modules link, the third passed: 14 node suites, nine scripts,
15/15 mutants killed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
# Row 80 (#1574) build, round 2
|
||||
|
||||
Status: Phase B complete on the full gate. Every suite, the nine scripts and the 15 mutants exited 0 (see "Sage's narrowed ruling and the full rerun" below). Earlier round 2 attempts stopped at cli for the missing `node_modules` and are recorded in the sections that follow. Not committed, not pushed, queue row not moved.
|
||||
|
||||
## Identity
|
||||
|
||||
- Row 80: bus, "stranded delivery" display. Brief: `docs/plans/2026-10-11_bus-stranded-delivery.md` (not edited).
|
||||
- Owner: researcher (decisions 122, 129). Reviewer: Filbert. Dispatch: Sage (lead decision 129).
|
||||
- BASE: `57a2ddc5978b05b780dc0e69fb4e6c2a60ab5877` (see `base.txt`). Round 1 used `2b2a9e14eec4ff81cdada5dc42bbf1b8063b29b4`; the rebase to 57a2ddc59 changed none of the three paths.
|
||||
- Worktree: `.claude/worktrees/agent-abe72e00ca2bcdf7e`, detached at BASE, changes uncommitted. No commit, no push, no queue move.
|
||||
- Candidate manifest: `candidate-manifest.sha256`, checked with `sha256sum -c` from the worktree root: all three paths OK.
|
||||
|
||||
| Path | sha256 |
|
||||
| --- | --- |
|
||||
| `packages/bus/README.md` | `02fae2939accd20f1032b0f5d4a3f4f3ca3b5da29606a6f4cee2b15a1da985b0` |
|
||||
| `packages/bus/src/broker.mjs` | `f685af1c5c496f0f1907995919017b9b098f7d84d9aa1047d126f3f4cc4991ef` |
|
||||
| `packages/bus/tests/agents.test.mjs` | `076137bca90b6594cc0888fb0daa2c57903fff5f1035122c00dd5540de65f6eb` |
|
||||
|
||||
`build.patch` is `git diff` against BASE, limited to these three paths (regenerated this round).
|
||||
|
||||
## Round 2 changes
|
||||
|
||||
- Round 1 Phase B failure: `bus` exit 1, 119/120. `agents.test.mjs:164` failed at L174. Cause: `node:sqlite` returns rows with a null prototype, so `assert.deepStrictEqual` against plain objects fails although the values match.
|
||||
- Fix (Sage's direction, in `broker.mjs`, not the test): the stranded query at L1048 to L1052 ends with `).map((m) => ({ id: m.id, from: m.from, run: m.run, at: m.at }));` (the `.map` is at L1052). The test expectation is unchanged.
|
||||
- `agents.test.mjs` file: `node --test tests/agents.test.mjs` passes 11/11 after the fix.
|
||||
- Mutant M15 added (`.map` removed, expected killer `agents.test.mjs:174 prototype check`). The table now has 15 entries.
|
||||
- `phase-b.sh` now stops on any nonzero step exit. A stop writes `STOP: <step> exited <rc>`, skips the rest, runs the cwd scan and ends `PHASE-B-STOPPED`. A full pass ends `PHASE-B-DONE`.
|
||||
- Stale round 1 logs in `out/` were deleted before the rerun.
|
||||
|
||||
## Phase B round 2 results
|
||||
|
||||
| Step | Exit | Tests | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `bus` | 0 | 120 pass, 0 fail | Round 1 was 119/120. The L174 failure is gone. |
|
||||
| `business` | 0 | 60 pass, 0 fail | |
|
||||
| `cli` | 1 | 131 total: 121 pass, 8 fail, 2 cancelled | Stop. See below. |
|
||||
| remaining 11 suites | not run | | Stopped |
|
||||
| nine `scripts/test-*.sh` | not run | | Stopped |
|
||||
| `mutants.py` (15) | not run | | Stopped |
|
||||
|
||||
### The cli stop
|
||||
|
||||
Cause: the worktree has no `node_modules`. The seat sandbox (`packages/seat/src/sandbox.mjs:95`, `binds`) calls `realpathSync` on `<worktree>/node_modules`, which fails with ENOENT. The failing trace runs through `packages/seat/src/session.mjs:122` and `packages/cli/src/launcher.mjs:237`. Two launcher tests then time out at 30 s (`tests/launcher.test.mjs:471`, the host that died hard). The rest of the failures are the same ENOENT.
|
||||
|
||||
Scope: the candidate touches only `packages/bus`, and no package imports another workspace package (this was checked in Phase A). The cli failure is therefore a property of the worktree layout, not of row 80. I have not proven it on BASE: I did not run cli on `57a2ddc59` in a clean tree, and I did not link `node_modules` into the worktree, because that would change the candidate tree and is a decision for Sage.
|
||||
|
||||
The cwd scan reported three processes with a cwd in the worktree when the stop fired (pids 1353827, 1359345, 1362919). A re-check a few seconds later found none. I did not identify them. They were most likely children of the failed cli run that exited on their own.
|
||||
|
||||
## Mutants (15), not run this round
|
||||
|
||||
Each mutant is one exact-once replacement in `broker.mjs`. Line numbers are in the candidate.
|
||||
|
||||
| ID | File:line | Expected killer |
|
||||
| --- | --- | --- |
|
||||
| M01 | broker.mjs:47 | `agents.test.mjs:203` length 50 |
|
||||
| M02 | broker.mjs:1048 | `agents.test.mjs:204` oldest-first ids |
|
||||
| M03 | broker.mjs:1048 | `agents.test.mjs:203` length 50 |
|
||||
| M04 | broker.mjs:1048 | `agents.test.mjs:189` current holder keeps unread |
|
||||
| M05 | broker.mjs:1048 | `agents.test.mjs:174` released-run entry |
|
||||
| M06 | broker.mjs:1048 | `agents.test.mjs:192` read message not listed |
|
||||
| M07 | broker.mjs:1048 | `agents.test.mjs:230` per business |
|
||||
| M08 | broker.mjs:1048 | `agents.test.mjs:221` per role |
|
||||
| M09 | broker.mjs:1048 | `agents.test.mjs:174` entry shape (`run` renamed) |
|
||||
| M10 | broker.mjs:1048 | `agents.test.mjs:174` entry shape (`from` renamed) |
|
||||
| M11 | broker.mjs:1048 | `agents.test.mjs:174` entry shape (`at` dropped) |
|
||||
| M12 | broker.mjs:1053 | `agents.test.mjs:58` key list |
|
||||
| M13 | broker.mjs:932 | `agents.test.mjs:62` role session keys |
|
||||
| M14 | broker.mjs:1048 | `agents.test.mjs:239` routed, not yet received |
|
||||
| M15 | broker.mjs:1052 | `agents.test.mjs:174` prototype check |
|
||||
|
||||
These expectations are reasoned, not observed. M09, M11 and M15 are the ones Sage asked about. They have not run against the fixed broker yet.
|
||||
|
||||
## Gate rerun after Sage's environment ruling (2026-10-13T15:13Z)
|
||||
|
||||
Sage ruled the cli stop an environment gap and asked for a symlink of the canonical root `node_modules` into the worktree, then a full rerun. Steps taken:
|
||||
|
||||
1. Cwd scan run from `/`: zero processes with a cwd in the worktree. An earlier count of one was my own scan subshell, which had inherited the worktree cwd. That process was gone on the re-check.
|
||||
2. `ln -s /mnt/storage/src/mosaic-stack/node_modules <worktree>/node_modules`. `git status --porcelain` listed `?? node_modules`. Sage's rule for that case is stop and report, so the rerun did not start. The symlink was removed with `rm` (link only). Status is back to the three candidate paths and the packet; the manifest still verifies. `build.patch` covers the three paths only, so the link never entered it.
|
||||
3. No rerun. Round 2 phase B results above stand as the last recorded run (bus 120/120, business 60/60, cli stopped at 121/131 with 8 fail and 2 cancelled, the rest not run).
|
||||
|
||||
Open for Sage: whether an untracked `node_modules` link is acceptable for the gate, or whether the gate should link it outside the worktree or exclude it from `git status`. The cli failures are not yet re-run with the link in place.
|
||||
|
||||
## Sage's narrowed ruling and the full rerun (2026-10-13)
|
||||
|
||||
Sage ruled that `?? node_modules` is expected: `.gitignore` line 2 is `node_modules/`, and a trailing slash matches only directories, so a symlink shows as untracked. Nothing is committed from the worktree, and `build.patch` and the manifest cover only the three paths. Stopping at the first symlink check was right under the earlier rule. The rerun was ordered.
|
||||
|
||||
Steps taken:
|
||||
1. Symlink linked again. `git status --porcelain` showed the three candidate paths, `?? node_modules`, and `?? agents/researcher/work/queue-80/`. Nothing else.
|
||||
2. First attempt did not start. A chained command was refused by the worktree guard, so `phase-b.sh` never ran. The earlier monitor read the previous round's summary. That monitor was stopped, the old out/ logs were removed, and the run was restarted with a plain command.
|
||||
3. Full phase B from the top, with the symlink in place.
|
||||
|
||||
Results (`out/phase-b-summary.txt`, end marker `PHASE-B-DONE`):
|
||||
|
||||
| Step | Exit |
|
||||
| --- | --- |
|
||||
| bus, business, cli, control-board, conversation, discord, harness, ledger, mosaic, queue, runs, seat, tasks, webui | 0 each |
|
||||
| `script-test-auth`, `-conductor`, `-config`, `-discord`, `-extension-package`, `-foundation`, `-queue`, `-release`, `-task` | 0 each |
|
||||
| `mutants.py` (15) | 0 |
|
||||
|
||||
Per-suite counts for bus, business and cli are in their logs under `out/`. The cli suite passes with the link in place, which confirms the round 2 cli failure was the missing `node_modules`. The bus suite (120/120) includes the `.map` fix.
|
||||
|
||||
Mutants: baseline green, manifest clean after restore, all 15 KILLED. Two notes on the verdicts:
|
||||
- M09, M10, M11 and M15 were all killed at `agents.test.mjs:174`, as expected.
|
||||
- M12 is killed, but not by the expected killer. Removing the `stranded` key makes the test read `undefined`, which throws a TypeError at `agents.test.mjs:203` and `:221` before the L58 key-list assertion runs. The verdict line says "error marker in log". The kill is real. The expected-killer note in the table is wrong about the mechanism.
|
||||
|
||||
Cleanup:
|
||||
- `rm <worktree>/node_modules` (plain rm, the link only) was run after the summary.
|
||||
- `git status --porcelain` shows only the three candidate paths and the packet.
|
||||
- `build.patch` covers the three paths only.
|
||||
- `sha256sum -c` verifies all three hashes.
|
||||
- A scan from `/` finds zero processes with a cwd in the worktree. Three transient processes flagged at the end of the run belonged to my own monitor's shell and its `sleep`, and they exited when the monitor stopped.
|
||||
|
||||
## Open items
|
||||
|
||||
1. Not committed, not pushed, queue row not moved.
|
||||
2. Sage to review M12's killer note (the mechanism differs from the table). The kill itself is sound.
|
||||
|
||||
## Round 1 (superseded)
|
||||
|
||||
Phase A packet, base `2b2a9e14`, 14 mutants. Its manifest sha256 was `4c91e42d71ff9a95e336db57f6c5821f3ef94f6e3dbf07ef860d71145d664c8a`, and its broker hash `cc949cea…` is superseded. Phase B round 1 failed at bus (119/120) as described above.
|
||||
@@ -0,0 +1 @@
|
||||
57a2ddc5978b05b780dc0e69fb4e6c2a60ab5877
|
||||
@@ -0,0 +1,176 @@
|
||||
diff --git a/packages/bus/README.md b/packages/bus/README.md
|
||||
index 527768b1a..ded8b9289 100644
|
||||
--- a/packages/bus/README.md
|
||||
+++ b/packages/bus/README.md
|
||||
@@ -108,7 +108,9 @@ committed. Callers must inspect its evidence before deciding what to do; they
|
||||
must not resend automatically. `message.receive` records delivery when the
|
||||
broker hands the batch to the connection, not when the harness acts on it.
|
||||
A lost response leaves an uncertain delivery, never automatic replay to a new
|
||||
-holder. `message.read` is an explicit acknowledgement. `trail(messageId)` lets
|
||||
+holder. A holder that releases a role without reading what it received leaves
|
||||
+those messages stranded: no later holder receives or reads them, and `agents`
|
||||
+lists them under `stranded` (Agent state, below). `message.read` is an explicit acknowledgement. `trail(messageId)` lets
|
||||
an operator inspect content and delivery evidence after an uncertain result.
|
||||
|
||||
| Verb | Arguments and result |
|
||||
@@ -127,7 +129,7 @@ an operator inspect content and delivery evidence after an uncertain result.
|
||||
| `launch.revoke`, `launch.restore` | No args; outside-agent human CLI only. |
|
||||
| `inbox` | No args; plain JSON projection scoped to capability's business. |
|
||||
| `tasks` | Optional args, scoped to capability's business (row 89, #1583). None: every row of the tasks projection (below). `{task_ref}` alone: that one row, or `task-not-found`. Any of `{done,assignee,label,requirement,limit,after}`: `{tasks,total,next}`, the short form (below). An unknown arg or bad value refuses `invalid-request`. |
|
||||
-| `agents` | No args; each held role's latest claim row in the capability's business. Human and reader sessions also get `state`, `working` and `inputNeeded` (Agent state, below); a role session gets the claim rows only. |
|
||||
+| `agents` | No args; each held role's latest claim row in the capability's business. Human and reader sessions also get `state`, `working`, `inputNeeded` and `stranded` (Agent state, below); a role session gets the claim rows only. |
|
||||
| `trail` | `{subject}`: task ref, decision UUID or message UUID. Linked records in broker write order. |
|
||||
| `decisions` | `{list}`: `roles` or `recent`; human and reader sessions only. Open decisions routed to a role, or the last 50 closed. |
|
||||
| `credentials` | No args; human and reader sessions only. `[{service,account,role,dateKind,date,state}]` for the capability's business, never a token, file path or variable name. |
|
||||
@@ -149,6 +151,12 @@ records the bus already keeps. Nothing new is written and no schema changed.
|
||||
- `state` is `working` when `working` is set, else `waiting` when
|
||||
`inputNeeded` is nonempty, else `idle`. Working wins over waiting, as on
|
||||
the control board.
|
||||
+- `stranded` lists messages to the role that an earlier holder was delivered
|
||||
+ and never read, as `{id, from, run, at}`: the sender (`human` for the CLI),
|
||||
+ the run it was delivered to, and when. Oldest first, at most 50, `[]` when
|
||||
+ none. The bus neither redelivers, routes nor marks these read, and the
|
||||
+ released run can no longer read them, so they stay listed. A person who
|
||||
+ still needs one sends it again as a new message.
|
||||
|
||||
Why the bus and not a seat-to-role map: the business file's `holder` is a
|
||||
display name, and the claim's holder is a run, not a seat, so no committed
|
||||
@@ -159,7 +167,8 @@ Limits: a message routed but not yet received shows idle. A reply that fails
|
||||
for a reason other than a gone session leaves its message unread, so the role
|
||||
stays working; `since` shows how long. No error or offline state is derived.
|
||||
A new holder doesn't inherit the previous holder's unread deliveries;
|
||||
-decisions stay with the role. Unheld and released roles aren't listed.
|
||||
+decisions stay with the role. Unheld and released roles aren't listed, so a
|
||||
+message stranded for a role nobody holds shows once a run claims the role.
|
||||
|
||||
Unknown keys, roles, references, actions and verbs refuse. If writing refusal
|
||||
evidence fails, the caller receives the static `storage-unavailable` code. `human` is the reserved
|
||||
diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs
|
||||
index 09a18633e..a7bb98576 100644
|
||||
--- a/packages/bus/src/broker.mjs
|
||||
+++ b/packages/bus/src/broker.mjs
|
||||
@@ -43,6 +43,8 @@ const CLOSED = "('resolved','withdrawn','expired')";
|
||||
export const RECENT = 50;
|
||||
// How many replies `replies` returns, oldest first.
|
||||
export const REPLIES = 50;
|
||||
+// Row 80 (#1574): how many stranded messages `agents` lists for each role, oldest first.
|
||||
+export const STRANDED = 50;
|
||||
// Row 89 (#1583): the arguments `tasks` takes, and its page size by default and at most.
|
||||
const TASK_ARGS = ['done', 'assignee', 'label', 'requirement', 'limit', 'after', 'task_ref'];
|
||||
export const TASK_PAGE = 50;
|
||||
@@ -1039,7 +1041,16 @@ export class Broker {
|
||||
.map((d) => ({ id: d.id, question: d.question, class: d.class, blocking: !!d.blocking, task_ref: d.task_ref, at: d.at }));
|
||||
const working = busy.messages ? { messages: busy.messages, since: busy.since } : null;
|
||||
const state = working ? 'working' : inputNeeded.length ? 'waiting' : 'idle';
|
||||
- return { ...c, state, working, inputNeeded };
|
||||
+ // Row 80 (#1574): messages to the role that an earlier holder was delivered and never read.
|
||||
+ // The run they went to no longer holds the role. A delivered row already exists, so routing
|
||||
+ // and receive skip them. Listing them writes nothing.
|
||||
+ const stranded = this.#store.all(
|
||||
+ `SELECT m.id, m.from_role AS "from", d.holder_run AS run, d.at FROM messages m JOIN deliveries d ON d.message=m.id AND d.op='delivered' WHERE m.business=? AND m.to_role=? AND d.holder_run IS NOT ? AND NOT EXISTS(SELECT 1 FROM deliveries r WHERE r.message=m.id AND r.op='read') ORDER BY m.seq LIMIT ${STRANDED}`,
|
||||
+ c.business,
|
||||
+ c.role,
|
||||
+ c.holder_run,
|
||||
+ ).map((m) => ({ id: m.id, from: m.from, run: m.run, at: m.at }));
|
||||
+ return { ...c, state, working, inputNeeded, stranded };
|
||||
}
|
||||
#claimEnd(s, h, op, decision) {
|
||||
this.#store.run(
|
||||
diff --git a/packages/bus/tests/agents.test.mjs b/packages/bus/tests/agents.test.mjs
|
||||
index 23b5126b3..d92933bd6 100644
|
||||
--- a/packages/bus/tests/agents.test.mjs
|
||||
+++ b/packages/bus/tests/agents.test.mjs
|
||||
@@ -55,7 +55,8 @@ test('agents: a held role with nothing on the bus is idle; a role session gets t
|
||||
['coder', 'idle', null, []],
|
||||
['pm', 'idle', null, []],
|
||||
]);
|
||||
- for (const r of rows) assert.deepEqual(Object.keys(r), [...CLAIM, 'state', 'working', 'inputNeeded']);
|
||||
+ for (const r of rows) assert.deepEqual(Object.keys(r), [...CLAIM, 'state', 'working', 'inputNeeded', 'stranded']);
|
||||
+ assert.deepEqual(rows.map((r) => r.stranded), [[], []]);
|
||||
assert.deepEqual(call(b, reader, 'agents'), rows);
|
||||
const plain = call(b, pm, 'agents');
|
||||
for (const r of plain) assert.deepEqual(Object.keys(r), CLAIM);
|
||||
@@ -157,3 +158,83 @@ test('agents: state reads only its own business, even for a run name both use',
|
||||
const there = byRole(call(b, otherHuman, 'agents')).cto;
|
||||
assert.deepEqual([there.state, there.working.messages, there.inputNeeded.length], ['working', 1, 1]);
|
||||
});
|
||||
+
|
||||
+// Row 80 (#1574): a message delivered to a run that released the role unread is stranded for the
|
||||
+// role. No later holder receives or reads it, and listing it writes no delivery row.
|
||||
+test('agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it', (t) => {
|
||||
+ const { b, store, agent, human } = setup(t);
|
||||
+ const first = agent('pm', 'demo', 'pm-one');
|
||||
+ const sent = call(b, human, 'message.send', { to: 'pm', body: 'left behind' });
|
||||
+ assert.deepEqual(call(b, first, 'message.receive').map((m) => m.id), [sent.id]);
|
||||
+ const before = store.all('SELECT * FROM deliveries');
|
||||
+ call(b, first, 'role.release');
|
||||
+ assert.deepEqual(call(b, human, 'agents'), [], 'a released role is not listed');
|
||||
+ const second = agent('pm', 'demo', 'pm-two');
|
||||
+ const row = byRole(call(b, human, 'agents')).pm;
|
||||
+ assert.deepEqual(row.stranded, [{ id: sent.id, from: 'human', run: 'pm-one', at: delivered(store, sent.id) }]);
|
||||
+ assert.deepEqual(call(b, second, 'message.receive'), []);
|
||||
+ assert.throws(() => call(b, second, 'message.read', { id: sent.id }), { code: 'not-recipient' });
|
||||
+ assert.throws(() => call(b, first, 'message.read', { id: sent.id }), { code: 'not-holder' });
|
||||
+ assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded, row.stranded);
|
||||
+ assert.deepEqual(store.all('SELECT * FROM deliveries'), before, 'the claim and the listing write no delivery row');
|
||||
+});
|
||||
+
|
||||
+test('agents: a message the holder read is not stranded; one the current holder has not read is not listed', (t) => {
|
||||
+ const { b, agent, human } = setup(t);
|
||||
+ const first = agent('pm', 'demo', 'pm-one');
|
||||
+ const read = call(b, human, 'message.send', { to: 'pm', body: 'read first' });
|
||||
+ const unread = call(b, human, 'message.send', { to: 'pm', body: 'still held' });
|
||||
+ call(b, first, 'message.receive');
|
||||
+ call(b, first, 'message.read', { id: read.id });
|
||||
+ assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded, [], 'the current holder still holds the unread one');
|
||||
+ call(b, first, 'role.release');
|
||||
+ agent('pm', 'demo', 'pm-two');
|
||||
+ assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded.map((m) => m.id), [unread.id]);
|
||||
+});
|
||||
+
|
||||
+test('agents: stranded is oldest first and capped at 50 for a role', (t) => {
|
||||
+ const { b, agent, human } = setup(t);
|
||||
+ const first = agent('pm', 'demo', 'pm-one');
|
||||
+ const ids = Array.from({ length: 60 }, (_, i) => call(b, human, 'message.send', { to: 'pm', body: `m${i}` }).id);
|
||||
+ assert.equal(call(b, first, 'message.receive').length, 60);
|
||||
+ call(b, first, 'role.release');
|
||||
+ agent('pm', 'demo', 'pm-two');
|
||||
+ const stranded = byRole(call(b, human, 'agents')).pm.stranded;
|
||||
+ assert.equal(stranded.length, 50);
|
||||
+ assert.deepEqual(stranded.map((m) => m.id), ids.slice(0, 50));
|
||||
+ assert.ok(stranded.every((m) => m.run === 'pm-one' && m.from === 'human'));
|
||||
+});
|
||||
+
|
||||
+test('agents: stranded is per role and per business', (t) => {
|
||||
+ const { b, agent, human } = setup(t);
|
||||
+ const cto = agent('cto', 'demo', 'cto-one');
|
||||
+ const pm = agent('pm', 'demo', 'pm-one');
|
||||
+ const toCto = call(b, human, 'message.send', { to: 'cto', body: 'for cto' });
|
||||
+ const toPm = call(b, human, 'message.send', { to: 'pm', body: 'for pm' });
|
||||
+ call(b, cto, 'message.receive');
|
||||
+ call(b, pm, 'message.receive');
|
||||
+ call(b, cto, 'role.release');
|
||||
+ call(b, pm, 'role.release');
|
||||
+ agent('cto', 'demo', 'cto-two');
|
||||
+ agent('pm', 'demo', 'pm-two');
|
||||
+ const rows = byRole(call(b, human, 'agents'));
|
||||
+ assert.deepEqual(rows.cto.stranded.map((m) => m.id), [toCto.id]);
|
||||
+ assert.deepEqual(rows.pm.stranded.map((m) => m.id), [toPm.id]);
|
||||
+ // The same role and run name in another business stays there.
|
||||
+ const otherHuman = b.bindHuman({ business: 'other', human: 'jason', via: 'cli', outsideAgent: true });
|
||||
+ const otherPm = agent('pm', 'other', 'pm-one');
|
||||
+ const elsewhere = call(b, otherHuman, 'message.send', { to: 'pm', body: 'elsewhere' });
|
||||
+ call(b, otherPm, 'message.receive');
|
||||
+ call(b, otherPm, 'role.release');
|
||||
+ agent('pm', 'other', 'pm-two');
|
||||
+ assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded.map((m) => m.id), [toPm.id]);
|
||||
+ assert.deepEqual(byRole(call(b, otherHuman, 'agents')).pm.stranded.map((m) => m.id), [elsewhere.id]);
|
||||
+});
|
||||
+
|
||||
+// Row 80 (#1574), Sage ruling: a message routed to a held role and not yet received is not stranded.
|
||||
+test('agents: a message routed to a held role and not yet received is not stranded', (t) => {
|
||||
+ const { b, agent, human } = setup(t);
|
||||
+ agent('pm', 'demo', 'pm-one');
|
||||
+ call(b, human, 'message.send', { to: 'pm', body: 'routed, not received' });
|
||||
+ assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded, []);
|
||||
+});
|
||||
@@ -0,0 +1,3 @@
|
||||
02fae2939accd20f1032b0f5d4a3f4f3ca3b5da29606a6f4cee2b15a1da985b0 packages/bus/README.md
|
||||
f685af1c5c496f0f1907995919017b9b098f7d84d9aa1047d126f3f4cc4991ef packages/bus/src/broker.mjs
|
||||
076137bca90b6594cc0888fb0daa2c57903fff5f1035122c00dd5540de65f6eb packages/bus/tests/agents.test.mjs
|
||||
@@ -0,0 +1,3 @@
|
||||
packages/bus/README.md
|
||||
packages/bus/src/broker.mjs
|
||||
packages/bus/tests/agents.test.mjs
|
||||
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env python3
|
||||
# Row 80 (#1574) mutation runner. Written in Phase A. NOT RUN.
|
||||
#
|
||||
# Phase B usage, from the canonical checkout, after "slot clear":
|
||||
# DOCKER_HOST=unix:///nonexistent.sock python3 -I <packet>/mutants.py <worktree> <packet>
|
||||
#
|
||||
# Every mutant rewrites one exact-once anchor in packages/bus/src/broker.mjs of the
|
||||
# worktree, runs tests/agents.test.mjs in its own process group under a timeout, and
|
||||
# restores the original bytes in a finally block. The manifest is checked before the
|
||||
# baseline and after the last restore. A red baseline aborts before any mutant.
|
||||
# A timeout is a failure, not a kill.
|
||||
#
|
||||
# Exit codes: 0 every mutant killed; 1 a mutant survived or a run timed out;
|
||||
# 2 baseline red, timed out, or manifest mismatch; 3 an anchor was not found exactly once.
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
TIMEOUT_S = 240
|
||||
BROKER = 'packages/bus/src/broker.mjs'
|
||||
SUITE = ['node', '--test', 'tests/agents.test.mjs']
|
||||
EXTRA_ENV = {'DOCKER_HOST': 'unix:///nonexistent.sock'}
|
||||
ERROR_MARKERS = ('SqliteError', 'RangeError', 'TypeError', 'ReferenceError', 'SyntaxError')
|
||||
|
||||
# (id, file, line in candidate, exact old text, exact new text, expected killer)
|
||||
MUTANTS = [
|
||||
('M01', BROKER, 47,
|
||||
'export const STRANDED = 50;',
|
||||
'export const STRANDED = 51;',
|
||||
'agents.test.mjs:203 length 50'),
|
||||
('M02', BROKER, 1048,
|
||||
'ORDER BY m.seq LIMIT ${STRANDED}',
|
||||
'ORDER BY m.seq DESC LIMIT ${STRANDED}',
|
||||
'agents.test.mjs:204 oldest-first ids'),
|
||||
('M03', BROKER, 1048,
|
||||
'ORDER BY m.seq LIMIT ${STRANDED}`',
|
||||
'ORDER BY m.seq`',
|
||||
'agents.test.mjs:203 length 50'),
|
||||
('M04', BROKER, 1048,
|
||||
'd.holder_run IS NOT ? AND NOT EXISTS',
|
||||
'(d.holder_run IS NOT ? OR 1=1) AND NOT EXISTS',
|
||||
'agents.test.mjs:189 current holder still holds the unread one'),
|
||||
('M05', BROKER, 1048,
|
||||
'd.holder_run IS NOT ? AND NOT EXISTS',
|
||||
'd.holder_run = ? AND NOT EXISTS',
|
||||
'agents.test.mjs:174 released-run entry'),
|
||||
('M06', BROKER, 1048,
|
||||
" AND NOT EXISTS(SELECT 1 FROM deliveries r WHERE r.message=m.id AND r.op='read') ORDER BY m.seq LIMIT ${STRANDED}",
|
||||
" ORDER BY m.seq LIMIT ${STRANDED}",
|
||||
'agents.test.mjs:192 read message must not be listed'),
|
||||
('M07', BROKER, 1048,
|
||||
'WHERE m.business=? AND m.to_role=? AND d.holder_run IS NOT ?',
|
||||
'WHERE (? IS NULL OR 1=1) AND m.to_role=? AND d.holder_run IS NOT ?',
|
||||
'agents.test.mjs:230 per business'),
|
||||
('M08', BROKER, 1048,
|
||||
'WHERE m.business=? AND m.to_role=? AND d.holder_run IS NOT ?',
|
||||
'WHERE m.business=? AND (? IS NULL OR 1=1) AND d.holder_run IS NOT ?',
|
||||
'agents.test.mjs:221 per role'),
|
||||
('M09', BROKER, 1048,
|
||||
'd.holder_run AS run, d.at FROM',
|
||||
'd.holder_run AS holder, d.at FROM',
|
||||
'agents.test.mjs:174 entry shape'),
|
||||
('M10', BROKER, 1048,
|
||||
'm.from_role AS "from"',
|
||||
'm.from_role AS "sender"',
|
||||
'agents.test.mjs:174 entry shape'),
|
||||
('M11', BROKER, 1048,
|
||||
'd.holder_run AS run, d.at FROM',
|
||||
'd.holder_run AS run FROM',
|
||||
'agents.test.mjs:174 entry shape'),
|
||||
('M12', BROKER, 1053,
|
||||
'return { ...c, state, working, inputNeeded, stranded };',
|
||||
'return { ...c, state, working, inputNeeded };',
|
||||
'agents.test.mjs:58 key list'),
|
||||
('M13', BROKER, 932,
|
||||
'return s.human || s.reader ? claims.map((c) => this.#agentState(c)) : claims;',
|
||||
'return claims.map((c) => this.#agentState(c));',
|
||||
'agents.test.mjs:62 role session keys'),
|
||||
('M14', BROKER, 1048,
|
||||
"JOIN deliveries d ON d.message=m.id AND d.op='delivered' WHERE m.business=? AND m.to_role=? AND d.holder_run IS NOT ?",
|
||||
"LEFT JOIN deliveries d ON d.message=m.id AND d.op='delivered' WHERE m.business=? AND m.to_role=? AND d.holder_run IS NOT ?",
|
||||
'agents.test.mjs:239 routed, not yet received, must not be listed'),
|
||||
('M15', BROKER, 1052,
|
||||
").map((m) => ({ id: m.id, from: m.from, run: m.run, at: m.at }));",
|
||||
");",
|
||||
'agents.test.mjs:174 prototype check'),
|
||||
]
|
||||
|
||||
|
||||
def sha256_of(path):
|
||||
digest = hashlib.sha256()
|
||||
with open(path, 'rb') as handle:
|
||||
for block in iter(lambda: handle.read(65536), b''):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def read_manifest(packet):
|
||||
entries = []
|
||||
with open(os.path.join(packet, 'candidate-manifest.sha256'), encoding='utf-8') as handle:
|
||||
for line in handle:
|
||||
line = line.rstrip('\n')
|
||||
if line:
|
||||
digest, rel = line.split(' ', 1)
|
||||
entries.append((digest, rel))
|
||||
return entries
|
||||
|
||||
|
||||
def manifest_mismatches(wt, entries):
|
||||
return [rel for digest, rel in entries if sha256_of(os.path.join(wt, rel)) != digest]
|
||||
|
||||
|
||||
def kill_group(pgid):
|
||||
try:
|
||||
os.killpg(pgid, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
|
||||
|
||||
def run_suite(wt, log_path):
|
||||
env = dict(os.environ, **EXTRA_ENV)
|
||||
with open(log_path, 'wb') as log:
|
||||
proc = subprocess.Popen(
|
||||
SUITE,
|
||||
cwd=os.path.join(wt, 'packages', 'bus'),
|
||||
env=env,
|
||||
stdout=log,
|
||||
stderr=subprocess.STDOUT,
|
||||
start_new_session=True,
|
||||
)
|
||||
try:
|
||||
return proc.wait(timeout=TIMEOUT_S), 'exit'
|
||||
except subprocess.TimeoutExpired:
|
||||
kill_group(proc.pid)
|
||||
proc.wait()
|
||||
return None, 'timeout'
|
||||
finally:
|
||||
kill_group(proc.pid)
|
||||
|
||||
|
||||
def worktree_leftovers(wt):
|
||||
# Reads /proc cwd links only. Reports; kills nothing outside the run's own group.
|
||||
found = []
|
||||
for pid in filter(str.isdigit, os.listdir('/proc')):
|
||||
try:
|
||||
cwd = os.readlink(os.path.join('/proc', pid, 'cwd'))
|
||||
except OSError:
|
||||
continue
|
||||
if cwd == wt or cwd.startswith(wt + os.sep):
|
||||
found.append((pid, cwd))
|
||||
return found
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
sys.stderr.write('usage: mutants.py <worktree> <packet_dir>\n')
|
||||
return 2
|
||||
wt = os.path.abspath(sys.argv[1])
|
||||
packet = os.path.abspath(sys.argv[2])
|
||||
out = os.path.join(packet, 'out')
|
||||
os.makedirs(out, exist_ok=True)
|
||||
entries = read_manifest(packet)
|
||||
|
||||
broken = manifest_mismatches(wt, entries)
|
||||
if broken:
|
||||
print('manifest mismatch before baseline:', broken)
|
||||
return 2
|
||||
|
||||
rc, status = run_suite(wt, os.path.join(out, 'baseline.log'))
|
||||
if status != 'exit' or rc != 0:
|
||||
print('baseline red or timed out:', status, rc)
|
||||
return 2
|
||||
|
||||
summary = ['baseline: green']
|
||||
failures = []
|
||||
anchor_error = False
|
||||
for mid, rel, line, old, new, killer in MUTANTS:
|
||||
path = os.path.join(wt, rel)
|
||||
with open(path, 'rb') as handle:
|
||||
original = handle.read()
|
||||
text = original.decode('utf-8')
|
||||
count = text.count(old)
|
||||
if count != 1:
|
||||
summary.append(f'{mid} {rel}:{line} ANCHOR count={count}; aborting run')
|
||||
anchor_error = True
|
||||
break
|
||||
log_path = os.path.join(out, f'{mid}.log')
|
||||
try:
|
||||
with open(path, 'wb') as handle:
|
||||
handle.write(text.replace(old, new, 1).encode('utf-8'))
|
||||
rc, status = run_suite(wt, log_path)
|
||||
finally:
|
||||
with open(path, 'wb') as handle:
|
||||
handle.write(original)
|
||||
with open(log_path, 'r', encoding='utf-8', errors='replace') as handle:
|
||||
log_text = handle.read()
|
||||
if status == 'timeout':
|
||||
verdict = 'TIMEOUT (counted as failure)'
|
||||
failures.append(mid)
|
||||
elif rc == 0:
|
||||
verdict = 'SURVIVED'
|
||||
failures.append(mid)
|
||||
elif any(marker in log_text for marker in ERROR_MARKERS):
|
||||
verdict = 'KILLED (error marker in log; check the reason)'
|
||||
else:
|
||||
verdict = 'KILLED'
|
||||
summary.append(f'{mid} {rel}:{line} {verdict}; expected killer: {killer}')
|
||||
|
||||
restored = manifest_mismatches(wt, entries)
|
||||
leftovers = worktree_leftovers(wt)
|
||||
summary.append(f'manifest after restore: {"clean" if not restored else restored}')
|
||||
summary.append(f'processes with cwd in worktree: {leftovers if leftovers else "none"}')
|
||||
with open(os.path.join(out, 'summary.txt'), 'w', encoding='utf-8') as handle:
|
||||
handle.write('\n'.join(summary) + '\n')
|
||||
print('\n'.join(summary))
|
||||
|
||||
if anchor_error:
|
||||
return 3
|
||||
if restored:
|
||||
return 2
|
||||
if failures:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,42 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (12.427972ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (11.244325ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (20.517617ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (8.45146ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (11.231301ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (10.056929ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (11.274098ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (11.709671ms)
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (54.239239ms)
|
||||
✔ agents: stranded is per role and per business (20.35104ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (7.009064ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 227.158892
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:195:1
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (54.239239ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
51 !== 50
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:203: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: 51,
|
||||
expected: 50,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (12.72117ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (11.395402ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (20.032914ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (8.238854ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (11.028392ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (11.61053ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (11.170498ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (10.712621ms)
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (57.724164ms)
|
||||
✔ agents: stranded is per role and per business (20.258686ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (7.0199ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 232.221665
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:195:1
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (57.724164ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
[
|
||||
+ '921a90e5-f360-4151-9a55-e9a64b655f95',
|
||||
+ '333f4be1-c781-47a6-8b8a-ef921339a473',
|
||||
+ 'df524a28-7966-4062-a38a-c8963cf15b3c',
|
||||
+ '3e973123-315e-4264-a430-6bedba9aec2d',
|
||||
+ '724eb099-52b6-4577-84b4-6c074729a1b0',
|
||||
+ '6194b870-139a-421f-8f95-ebde8c859928',
|
||||
+ 'ba6ac208-3638-44f5-ba9a-4ba519325ebb',
|
||||
+ '048be485-48f8-4af0-b9b5-202f8be9e883',
|
||||
+ 'd8ab5ff5-a0db-44b5-95fb-48be1ac6dd14',
|
||||
+ '22307c19-cf80-4b76-a64c-31e271577baa',
|
||||
+ 'fd35133a-c609-449d-96db-d0b0113d1f4c',
|
||||
+ 'f3f83d1b-fa2a-4432-8bdb-76864d709f46',
|
||||
+ '03eee571-16d9-4014-8e19-977f10de83b1',
|
||||
+ '767855f3-d98e-4879-a6d1-c5edd4a0f861',
|
||||
+ '3736dfee-04f6-4841-ae95-2189c9ad40e7',
|
||||
+ 'a2e15f1e-02b6-4c22-a1ee-27598fc355d7',
|
||||
+ '95abc82f-2981-482d-96eb-7ebd961d5b76',
|
||||
+ '29d8cb04-3d57-417c-b450-ef2f6494ca81',
|
||||
+ 'bae8766e-93a8-4adf-a9f8-38ca5f51ceab',
|
||||
+ 'd4d8e29c-3d37-4141-8697-e1331c267eed',
|
||||
+ '4c0cb465-b2c9-4aa2-ae7c-6fe47e295c6c',
|
||||
+ 'a6c97ff8-3437-49c2-9c0e-cfdfaed75860',
|
||||
+ 'acfae231-45f4-4d18-87f5-f35fbaf01671',
|
||||
+ '4bd079ee-68f5-4bf7-9e52-67bfa1628aa2',
|
||||
+ '10a77d20-901f-4281-a236-1a4368c9e325',
|
||||
+ '0035176d-ca9b-4d80-81dd-00ace511ee08',
|
||||
+ '3cabc3cf-b1e0-4d80-b07f-7b5d1b7c4958',
|
||||
+ '5e6720ce-7095-4b93-b7f9-739e3d830ce3',
|
||||
+ 'f0d70708-0e42-4277-8685-fc3db96e86e1',
|
||||
+ '7c251925-7244-4078-bc59-a7f57239ab82',
|
||||
+ '2b7d423a-8b1f-498c-9fa7-7ad8255aa4e9',
|
||||
+ '7bea63ad-2d98-4a95-afbb-99f1bd016444',
|
||||
+ '837108c2-a7ef-4c5e-967f-993dee0a5bfb',
|
||||
+ '7653ffb5-976d-4925-82ba-ad2e2807ea6b',
|
||||
+ '5a9ab31b-f85f-4fb5-8a8f-25c1340afdbc',
|
||||
+ 'b3ba82e3-98e3-4128-9e4e-d4666d6704b8',
|
||||
+ '097663ab-c593-45fb-8ac0-b71e3501e9bb',
|
||||
+ 'fcff94e1-4a89-4801-83ae-c64c5ae78012',
|
||||
+ 'bd951fe9-bf98-48ed-84ea-7b3c343ee765',
|
||||
+ '4cc5a576-793d-40fc-a9b9-a299520631fe',
|
||||
+ 'b85d495e-e19c-44e7-96cd-0f6eea773770',
|
||||
+ '50da4420-ecaa-42fe-b162-4ebdd925fdbb',
|
||||
+ '6415fb96-a1f7-4714-962a-fa95e23b3524',
|
||||
+ 'aba5b11e-56cf-4d61-aee4-3c121920ec5e',
|
||||
+ 'bb293064-26e2-4b05-8d39-ea0c7403c33e',
|
||||
+ 'e9a1f205-5f98-4bf5-bf34-d224ba948b38',
|
||||
+ '3ee22153-48eb-41f8-8e52-284212cf3cd9',
|
||||
+ 'dac25771-850e-4b82-9bb1-a4d9b0fd676c',
|
||||
+ '0318e7bf-35b0-4426-9eb3-6fe9d4220f90',
|
||||
- 'fc1480f8-2cb0-4c01-aebb-33eff6513221',
|
||||
- '24f06532-d1bb-4df6-b8ca-38fd80bd5551',
|
||||
- 'b68b6642-6d4d-48d8-b195-9cc64323eab3',
|
||||
- 'e8b92bc3-3b7e-4413-a670-280f66db805b',
|
||||
- '74c0cf6b-dba6-43f1-b4f4-831c7ee26647',
|
||||
- '4c856473-0106-4f27-bee5-2fbd9470dde6',
|
||||
- '0e8c57a0-b02e-4ce5-918e-728c9b006c12',
|
||||
- '43465b09-61bb-46ca-9823-2c03ecd76dc0',
|
||||
- '3a951aec-851e-4745-ae9e-918cb1f2a136',
|
||||
- '50f8f742-1528-475c-bfa7-8672c8db111c',
|
||||
'722325cd-54fa-481a-ab83-851edb7273be',
|
||||
- '0318e7bf-35b0-4426-9eb3-6fe9d4220f90',
|
||||
- 'dac25771-850e-4b82-9bb1-a4d9b0fd676c',
|
||||
- '3ee22153-48eb-41f8-8e52-284212cf3cd9',
|
||||
- 'e9a1f205-5f98-4bf5-bf34-d224ba948b38',
|
||||
- 'bb293064-26e2-4b05-8d39-ea0c7403c33e',
|
||||
- 'aba5b11e-56cf-4d61-aee4-3c121920ec5e',
|
||||
- '6415fb96-a1f7-4714-962a-fa95e23b3524',
|
||||
- '50da4420-ecaa-42fe-b162-4ebdd925fdbb',
|
||||
- 'b85d495e-e19c-44e7-96cd-0f6eea773770',
|
||||
- '4cc5a576-793d-40fc-a9b9-a299520631fe',
|
||||
- 'bd951fe9-bf98-48ed-84ea-7b3c343ee765',
|
||||
- 'fcff94e1-4a89-4801-83ae-c64c5ae78012',
|
||||
- '097663ab-c593-45fb-8ac0-b71e3501e9bb',
|
||||
- 'b3ba82e3-98e3-4128-9e4e-d4666d6704b8',
|
||||
- '5a9ab31b-f85f-4fb5-8a8f-25c1340afdbc',
|
||||
- '7653ffb5-976d-4925-82ba-ad2e2807ea6b',
|
||||
- '837108c2-a7ef-4c5e-967f-993dee0a5bfb',
|
||||
- '7bea63ad-2d98-4a95-afbb-99f1bd016444',
|
||||
- '2b7d423a-8b1f-498c-9fa7-7ad8255aa4e9',
|
||||
- '7c251925-7244-4078-bc59-a7f57239ab82',
|
||||
- 'f0d70708-0e42-4277-8685-fc3db96e86e1',
|
||||
- '5e6720ce-7095-4b93-b7f9-739e3d830ce3',
|
||||
- '3cabc3cf-b1e0-4d80-b07f-7b5d1b7c4958',
|
||||
- '0035176d-ca9b-4d80-81dd-00ace511ee08',
|
||||
- '10a77d20-901f-4281-a236-1a4368c9e325',
|
||||
- '4bd079ee-68f5-4bf7-9e52-67bfa1628aa2',
|
||||
- 'acfae231-45f4-4d18-87f5-f35fbaf01671',
|
||||
- 'a6c97ff8-3437-49c2-9c0e-cfdfaed75860',
|
||||
- '4c0cb465-b2c9-4aa2-ae7c-6fe47e295c6c',
|
||||
- 'd4d8e29c-3d37-4141-8697-e1331c267eed',
|
||||
- 'bae8766e-93a8-4adf-a9f8-38ca5f51ceab',
|
||||
- '29d8cb04-3d57-417c-b450-ef2f6494ca81',
|
||||
- '95abc82f-2981-482d-96eb-7ebd961d5b76',
|
||||
- 'a2e15f1e-02b6-4c22-a1ee-27598fc355d7',
|
||||
- '3736dfee-04f6-4841-ae95-2189c9ad40e7',
|
||||
- '767855f3-d98e-4879-a6d1-c5edd4a0f861',
|
||||
- '03eee571-16d9-4014-8e19-977f10de83b1',
|
||||
- 'f3f83d1b-fa2a-4432-8bdb-76864d709f46',
|
||||
- 'fd35133a-c609-449d-96db-d0b0113d1f4c'
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:204: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: [
|
||||
'921a90e5-f360-4151-9a55-e9a64b655f95', '333f4be1-c781-47a6-8b8a-ef921339a473',
|
||||
'df524a28-7966-4062-a38a-c8963cf15b3c', '3e973123-315e-4264-a430-6bedba9aec2d',
|
||||
'724eb099-52b6-4577-84b4-6c074729a1b0', '6194b870-139a-421f-8f95-ebde8c859928',
|
||||
'ba6ac208-3638-44f5-ba9a-4ba519325ebb', '048be485-48f8-4af0-b9b5-202f8be9e883',
|
||||
'd8ab5ff5-a0db-44b5-95fb-48be1ac6dd14', '22307c19-cf80-4b76-a64c-31e271577baa',
|
||||
'fd35133a-c609-449d-96db-d0b0113d1f4c', 'f3f83d1b-fa2a-4432-8bdb-76864d709f46',
|
||||
'03eee571-16d9-4014-8e19-977f10de83b1', '767855f3-d98e-4879-a6d1-c5edd4a0f861',
|
||||
'3736dfee-04f6-4841-ae95-2189c9ad40e7', 'a2e15f1e-02b6-4c22-a1ee-27598fc355d7',
|
||||
'95abc82f-2981-482d-96eb-7ebd961d5b76', '29d8cb04-3d57-417c-b450-ef2f6494ca81',
|
||||
'bae8766e-93a8-4adf-a9f8-38ca5f51ceab', 'd4d8e29c-3d37-4141-8697-e1331c267eed',
|
||||
'4c0cb465-b2c9-4aa2-ae7c-6fe47e295c6c', 'a6c97ff8-3437-49c2-9c0e-cfdfaed75860',
|
||||
'acfae231-45f4-4d18-87f5-f35fbaf01671', '4bd079ee-68f5-4bf7-9e52-67bfa1628aa2',
|
||||
'10a77d20-901f-4281-a236-1a4368c9e325', '0035176d-ca9b-4d80-81dd-00ace511ee08',
|
||||
'3cabc3cf-b1e0-4d80-b07f-7b5d1b7c4958', '5e6720ce-7095-4b93-b7f9-739e3d830ce3',
|
||||
'f0d70708-0e42-4277-8685-fc3db96e86e1', '7c251925-7244-4078-bc59-a7f57239ab82',
|
||||
'2b7d423a-8b1f-498c-9fa7-7ad8255aa4e9', '7bea63ad-2d98-4a95-afbb-99f1bd016444',
|
||||
'837108c2-a7ef-4c5e-967f-993dee0a5bfb', '7653ffb5-976d-4925-82ba-ad2e2807ea6b',
|
||||
'5a9ab31b-f85f-4fb5-8a8f-25c1340afdbc', 'b3ba82e3-98e3-4128-9e4e-d4666d6704b8',
|
||||
'097663ab-c593-45fb-8ac0-b71e3501e9bb', 'fcff94e1-4a89-4801-83ae-c64c5ae78012',
|
||||
'bd951fe9-bf98-48ed-84ea-7b3c343ee765', '4cc5a576-793d-40fc-a9b9-a299520631fe',
|
||||
'b85d495e-e19c-44e7-96cd-0f6eea773770', '50da4420-ecaa-42fe-b162-4ebdd925fdbb',
|
||||
'6415fb96-a1f7-4714-962a-fa95e23b3524', 'aba5b11e-56cf-4d61-aee4-3c121920ec5e',
|
||||
'bb293064-26e2-4b05-8d39-ea0c7403c33e', 'e9a1f205-5f98-4bf5-bf34-d224ba948b38',
|
||||
'3ee22153-48eb-41f8-8e52-284212cf3cd9', 'dac25771-850e-4b82-9bb1-a4d9b0fd676c',
|
||||
'0318e7bf-35b0-4426-9eb3-6fe9d4220f90', '722325cd-54fa-481a-ab83-851edb7273be'
|
||||
],
|
||||
expected: [
|
||||
'fc1480f8-2cb0-4c01-aebb-33eff6513221', '24f06532-d1bb-4df6-b8ca-38fd80bd5551',
|
||||
'b68b6642-6d4d-48d8-b195-9cc64323eab3', 'e8b92bc3-3b7e-4413-a670-280f66db805b',
|
||||
'74c0cf6b-dba6-43f1-b4f4-831c7ee26647', '4c856473-0106-4f27-bee5-2fbd9470dde6',
|
||||
'0e8c57a0-b02e-4ce5-918e-728c9b006c12', '43465b09-61bb-46ca-9823-2c03ecd76dc0',
|
||||
'3a951aec-851e-4745-ae9e-918cb1f2a136', '50f8f742-1528-475c-bfa7-8672c8db111c',
|
||||
'722325cd-54fa-481a-ab83-851edb7273be', '0318e7bf-35b0-4426-9eb3-6fe9d4220f90',
|
||||
'dac25771-850e-4b82-9bb1-a4d9b0fd676c', '3ee22153-48eb-41f8-8e52-284212cf3cd9',
|
||||
'e9a1f205-5f98-4bf5-bf34-d224ba948b38', 'bb293064-26e2-4b05-8d39-ea0c7403c33e',
|
||||
'aba5b11e-56cf-4d61-aee4-3c121920ec5e', '6415fb96-a1f7-4714-962a-fa95e23b3524',
|
||||
'50da4420-ecaa-42fe-b162-4ebdd925fdbb', 'b85d495e-e19c-44e7-96cd-0f6eea773770',
|
||||
'4cc5a576-793d-40fc-a9b9-a299520631fe', 'bd951fe9-bf98-48ed-84ea-7b3c343ee765',
|
||||
'fcff94e1-4a89-4801-83ae-c64c5ae78012', '097663ab-c593-45fb-8ac0-b71e3501e9bb',
|
||||
'b3ba82e3-98e3-4128-9e4e-d4666d6704b8', '5a9ab31b-f85f-4fb5-8a8f-25c1340afdbc',
|
||||
'7653ffb5-976d-4925-82ba-ad2e2807ea6b', '837108c2-a7ef-4c5e-967f-993dee0a5bfb',
|
||||
'7bea63ad-2d98-4a95-afbb-99f1bd016444', '2b7d423a-8b1f-498c-9fa7-7ad8255aa4e9',
|
||||
'7c251925-7244-4078-bc59-a7f57239ab82', 'f0d70708-0e42-4277-8685-fc3db96e86e1',
|
||||
'5e6720ce-7095-4b93-b7f9-739e3d830ce3', '3cabc3cf-b1e0-4d80-b07f-7b5d1b7c4958',
|
||||
'0035176d-ca9b-4d80-81dd-00ace511ee08', '10a77d20-901f-4281-a236-1a4368c9e325',
|
||||
'4bd079ee-68f5-4bf7-9e52-67bfa1628aa2', 'acfae231-45f4-4d18-87f5-f35fbaf01671',
|
||||
'a6c97ff8-3437-49c2-9c0e-cfdfaed75860', '4c0cb465-b2c9-4aa2-ae7c-6fe47e295c6c',
|
||||
'd4d8e29c-3d37-4141-8697-e1331c267eed', 'bae8766e-93a8-4adf-a9f8-38ca5f51ceab',
|
||||
'29d8cb04-3d57-417c-b450-ef2f6494ca81', '95abc82f-2981-482d-96eb-7ebd961d5b76',
|
||||
'a2e15f1e-02b6-4c22-a1ee-27598fc355d7', '3736dfee-04f6-4841-ae95-2189c9ad40e7',
|
||||
'767855f3-d98e-4879-a6d1-c5edd4a0f861', '03eee571-16d9-4014-8e19-977f10de83b1',
|
||||
'f3f83d1b-fa2a-4432-8bdb-76864d709f46', 'fd35133a-c609-449d-96db-d0b0113d1f4c'
|
||||
],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (12.831352ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (12.972808ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (21.165045ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (9.456782ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (11.725813ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (10.250449ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (11.224188ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (10.601091ms)
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (53.216083ms)
|
||||
✔ agents: stranded is per role and per business (19.265168ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (6.826504ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 231.87109
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:195:1
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (53.216083ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
60 !== 50
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:203: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: 60,
|
||||
expected: 50,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (13.947685ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (13.713072ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (22.704203ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (10.141467ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (11.792578ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (11.232174ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (12.327879ms)
|
||||
✖ agents: a message the holder read is not stranded; one the current holder has not read is not listed (9.611499ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (56.212521ms)
|
||||
✔ agents: stranded is per role and per business (20.96181ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (6.840827ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 242.601653
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:182:1
|
||||
✖ agents: a message the holder read is not stranded; one the current holder has not read is not listed (9.611499ms)
|
||||
AssertionError [ERR_ASSERTION]: the current holder still holds the unread one
|
||||
+ actual - expected
|
||||
|
||||
+ [
|
||||
+ {
|
||||
+ at: '2026-10-13T15:17:44.690Z',
|
||||
+ from: 'human',
|
||||
+ id: '9c430ecf-7255-4ddc-99d8-f1c2c65a1ab4',
|
||||
+ run: 'pm-one'
|
||||
+ }
|
||||
+ ]
|
||||
- []
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:189:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: [ { id: '9c430ecf-7255-4ddc-99d8-f1c2c65a1ab4', from: 'human', run: 'pm-one', at: '2026-10-13T15:17:44.690Z' } ],
|
||||
expected: [],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (13.291284ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (13.081199ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (20.341381ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (8.758325ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (10.684116ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (10.403954ms)
|
||||
✖ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (10.534107ms)
|
||||
✖ agents: a message the holder read is not stranded; one the current holder has not read is not listed (9.2386ms)
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (57.222255ms)
|
||||
✖ agents: stranded is per role and per business (14.225381ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (7.728053ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 7
|
||||
ℹ fail 4
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 226.297661
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:164:1
|
||||
✖ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (10.534107ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ []
|
||||
- [
|
||||
- {
|
||||
- at: '2026-10-13T15:17:44.980Z',
|
||||
- from: 'human',
|
||||
- id: '85767bc6-0b64-440a-941f-282238103291',
|
||||
- run: 'pm-one'
|
||||
- }
|
||||
- ]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:174: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: [],
|
||||
expected: [ { id: '85767bc6-0b64-440a-941f-282238103291', from: 'human', run: 'pm-one', at: '2026-10-13T15:17:44.980Z' } ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at tests/agents.test.mjs:182:1
|
||||
✖ agents: a message the holder read is not stranded; one the current holder has not read is not listed (9.2386ms)
|
||||
AssertionError [ERR_ASSERTION]: the current holder still holds the unread one
|
||||
+ actual - expected
|
||||
|
||||
+ [
|
||||
+ {
|
||||
+ at: '2026-10-13T15:17:44.995Z',
|
||||
+ from: 'human',
|
||||
+ id: '390af4f1-75b3-43a5-b837-acbc26a29622',
|
||||
+ run: 'pm-one'
|
||||
+ }
|
||||
+ ]
|
||||
- []
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:189:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: [ { id: '390af4f1-75b3-43a5-b837-acbc26a29622', from: 'human', run: 'pm-one', at: '2026-10-13T15:17:44.995Z' } ],
|
||||
expected: [],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at tests/agents.test.mjs:195:1
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (57.222255ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
0 !== 50
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:203: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: 0,
|
||||
expected: 50,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at tests/agents.test.mjs:208:1
|
||||
✖ agents: stranded is per role and per business (14.225381ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ []
|
||||
- [
|
||||
- '3b3bf3e4-3805-4189-a2b0-fe18d84024e5'
|
||||
- ]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:221: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: [],
|
||||
expected: [ '3b3bf3e4-3805-4189-a2b0-fe18d84024e5' ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (12.575404ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (12.791608ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (22.483467ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (8.746898ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (11.180947ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (10.389365ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (11.45335ms)
|
||||
✖ agents: a message the holder read is not stranded; one the current holder has not read is not listed (11.915539ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (56.588921ms)
|
||||
✔ agents: stranded is per role and per business (24.546573ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (7.063548ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 242.574414
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:182:1
|
||||
✖ agents: a message the holder read is not stranded; one the current holder has not read is not listed (11.915539ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
[
|
||||
+ 'a32b9442-8e8f-4388-88bf-05ffda33a6d9',
|
||||
'1ed2fb08-70f5-4c4c-9117-0190ac413206'
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:192: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: [ 'a32b9442-8e8f-4388-88bf-05ffda33a6d9', '1ed2fb08-70f5-4c4c-9117-0190ac413206' ],
|
||||
expected: [ '1ed2fb08-70f5-4c4c-9117-0190ac413206' ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (12.037264ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (11.544025ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (21.868763ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (8.883976ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (10.878633ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (9.993626ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (11.301492ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (10.83215ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (56.711544ms)
|
||||
✖ agents: stranded is per role and per business (19.902413ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (7.803048ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 235.016841
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:208:1
|
||||
✖ agents: stranded is per role and per business (19.902413ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
[
|
||||
'03d641ed-cc39-45ac-b469-54f5d3034ed3',
|
||||
+ 'd06be9e6-f8c5-4b51-8e35-eb94a446b35f'
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:230: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: [ '03d641ed-cc39-45ac-b469-54f5d3034ed3', 'd06be9e6-f8c5-4b51-8e35-eb94a446b35f' ],
|
||||
expected: [ '03d641ed-cc39-45ac-b469-54f5d3034ed3' ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (12.093453ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (11.341197ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (19.762249ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (9.048559ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (12.438823ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (11.137217ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (11.671635ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (11.214157ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (56.551746ms)
|
||||
✖ agents: stranded is per role and per business (16.162178ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (8.651157ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 229.625348
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:208:1
|
||||
✖ agents: stranded is per role and per business (16.162178ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
[
|
||||
'6186ae78-bed3-40e6-94da-74b9f860d85f',
|
||||
+ '4d4165c5-2caa-4a31-af64-3a40458d2b8b'
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:221: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: [ '6186ae78-bed3-40e6-94da-74b9f860d85f', '4d4165c5-2caa-4a31-af64-3a40458d2b8b' ],
|
||||
expected: [ '6186ae78-bed3-40e6-94da-74b9f860d85f' ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (13.449387ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (13.125393ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (21.916817ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (8.995578ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (10.475307ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (9.968012ms)
|
||||
✖ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (10.518486ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (11.088209ms)
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (61.366205ms)
|
||||
✔ agents: stranded is per role and per business (20.999066ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (7.807976ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 9
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 241.639854
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:164:1
|
||||
✖ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (10.518486ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
[
|
||||
{
|
||||
at: '2026-10-13T15:17:46.094Z',
|
||||
from: 'human',
|
||||
id: 'be7c27fd-2c60-4072-8f87-2f44c53d14ba',
|
||||
+ run: undefined
|
||||
- run: 'pm-one'
|
||||
}
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:174: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: [ { id: 'be7c27fd-2c60-4072-8f87-2f44c53d14ba', from: 'human', run: undefined, at: '2026-10-13T15:17:46.094Z' } ],
|
||||
expected: [ { id: 'be7c27fd-2c60-4072-8f87-2f44c53d14ba', from: 'human', run: 'pm-one', at: '2026-10-13T15:17:46.094Z' } ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at tests/agents.test.mjs:195:1
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (61.366205ms)
|
||||
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
|
||||
|
||||
assert.ok(stranded.every((m) => m.run === 'pm-one' && m.from === 'human'))
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:205: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: false,
|
||||
expected: true,
|
||||
operator: '==',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (12.66402ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (11.693831ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (20.718132ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (8.962495ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (10.736422ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (13.707732ms)
|
||||
✖ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (10.445493ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (10.798302ms)
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (57.113075ms)
|
||||
✔ agents: stranded is per role and per business (18.370781ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (7.030385ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 9
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 232.782853
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:164:1
|
||||
✖ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (10.445493ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
[
|
||||
{
|
||||
at: '2026-10-13T15:17:46.407Z',
|
||||
+ from: undefined,
|
||||
- from: 'human',
|
||||
id: '86e7db9d-97de-45d7-a667-3cef1ef93ae8',
|
||||
run: 'pm-one'
|
||||
}
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:174: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: [ { id: '86e7db9d-97de-45d7-a667-3cef1ef93ae8', from: undefined, run: 'pm-one', at: '2026-10-13T15:17:46.407Z' } ],
|
||||
expected: [ { id: '86e7db9d-97de-45d7-a667-3cef1ef93ae8', from: 'human', run: 'pm-one', at: '2026-10-13T15:17:46.407Z' } ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at tests/agents.test.mjs:195:1
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (57.113075ms)
|
||||
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
|
||||
|
||||
assert.ok(stranded.every((m) => m.run === 'pm-one' && m.from === 'human'))
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:205: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: false,
|
||||
expected: true,
|
||||
operator: '==',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (13.293506ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (13.075346ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (22.398787ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (9.156459ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (10.852179ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (10.371864ms)
|
||||
✖ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (10.333468ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (10.801328ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (55.983646ms)
|
||||
✔ agents: stranded is per role and per business (18.674178ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (6.860906ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 233.138123
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:164:1
|
||||
✖ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (10.333468ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
[
|
||||
{
|
||||
+ at: undefined,
|
||||
- at: '2026-10-13T15:17:46.675Z',
|
||||
from: 'human',
|
||||
id: '73b32117-fd23-4e40-97d4-b1e156b134f5',
|
||||
run: 'pm-one'
|
||||
}
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:174: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: [ { id: '73b32117-fd23-4e40-97d4-b1e156b134f5', from: 'human', run: 'pm-one', at: undefined } ],
|
||||
expected: [ { id: '73b32117-fd23-4e40-97d4-b1e156b134f5', from: 'human', run: 'pm-one', at: '2026-10-13T15:17:46.675Z' } ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
✖ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (12.474894ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (12.153755ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (21.049513ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (9.88127ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (11.308885ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (11.343344ms)
|
||||
✖ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (10.062934ms)
|
||||
✖ agents: a message the holder read is not stranded; one the current holder has not read is not listed (8.937058ms)
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (53.693012ms)
|
||||
✖ agents: stranded is per role and per business (14.283581ms)
|
||||
✖ agents: a message routed to a held role and not yet received is not stranded (7.958035ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ fail 6
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 226.657769
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:49:1
|
||||
✖ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (12.474894ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
... Skipped lines
|
||||
|
||||
[
|
||||
'seq',
|
||||
'at',
|
||||
'business',
|
||||
'role',
|
||||
...
|
||||
'inputNeeded',
|
||||
- 'stranded'
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:58:32)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: [
|
||||
'seq', 'at',
|
||||
'business', 'role',
|
||||
'op', 'holder_run',
|
||||
'harness', 'address',
|
||||
'by', 'reason',
|
||||
'decision', 'state',
|
||||
'working', 'inputNeeded'
|
||||
],
|
||||
expected: [
|
||||
'seq', 'at',
|
||||
'business', 'role',
|
||||
'op', 'holder_run',
|
||||
'harness', 'address',
|
||||
'by', 'reason',
|
||||
'decision', 'state',
|
||||
'working', 'inputNeeded',
|
||||
'stranded'
|
||||
],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at tests/agents.test.mjs:164:1
|
||||
✖ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (10.062934ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ undefined
|
||||
- [
|
||||
- {
|
||||
- at: '2026-10-13T15:17:46.940Z',
|
||||
- from: 'human',
|
||||
- id: '7e46059e-eed8-409d-be84-8798b1926625',
|
||||
- run: 'pm-one'
|
||||
- }
|
||||
- ]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:174: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: [ { id: '7e46059e-eed8-409d-be84-8798b1926625', from: 'human', run: 'pm-one', at: '2026-10-13T15:17:46.940Z' } ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at tests/agents.test.mjs:182:1
|
||||
✖ agents: a message the holder read is not stranded; one the current holder has not read is not listed (8.937058ms)
|
||||
AssertionError [ERR_ASSERTION]: the current holder still holds the unread one
|
||||
|
||||
undefined !== []
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:189:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: [],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at tests/agents.test.mjs:195:1
|
||||
✖ agents: stranded is oldest first and capped at 50 for a role (53.693012ms)
|
||||
TypeError: Cannot read properties of undefined (reading 'length')
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:203:25)
|
||||
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)
|
||||
|
||||
test at tests/agents.test.mjs:208:1
|
||||
✖ agents: stranded is per role and per business (14.283581ms)
|
||||
TypeError: Cannot read properties of undefined (reading 'map')
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:221:38)
|
||||
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)
|
||||
|
||||
test at tests/agents.test.mjs:235:1
|
||||
✖ agents: a message routed to a held role and not yet received is not stranded (7.958035ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
|
||||
undefined !== []
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:239: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: [],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
✖ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (13.20614ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (11.792798ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (19.073256ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (8.500086ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (11.26376ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (11.0725ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (11.66001ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (11.494842ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (54.116447ms)
|
||||
✔ agents: stranded is per role and per business (19.406198ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (7.232445ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 233.129566
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:49:1
|
||||
✖ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (13.20614ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
... Skipped lines
|
||||
|
||||
[
|
||||
'seq',
|
||||
'at',
|
||||
'business',
|
||||
'role',
|
||||
...
|
||||
'decision',
|
||||
+ 'state',
|
||||
+ 'working',
|
||||
+ 'inputNeeded',
|
||||
+ 'stranded'
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:62:33)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: [
|
||||
'seq', 'at',
|
||||
'business', 'role',
|
||||
'op', 'holder_run',
|
||||
'harness', 'address',
|
||||
'by', 'reason',
|
||||
'decision', 'state',
|
||||
'working', 'inputNeeded',
|
||||
'stranded'
|
||||
],
|
||||
expected: [
|
||||
'seq', 'at',
|
||||
'business', 'role',
|
||||
'op', 'holder_run',
|
||||
'harness', 'address',
|
||||
'by', 'reason',
|
||||
'decision'
|
||||
],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (12.546173ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (11.501785ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (20.617157ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (9.011495ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (10.999799ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (13.891679ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (11.632041ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (10.863952ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (56.625833ms)
|
||||
✔ agents: stranded is per role and per business (19.232976ms)
|
||||
✖ agents: a message routed to a held role and not yet received is not stranded (8.101828ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 235.271076
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:235:1
|
||||
✖ agents: a message routed to a held role and not yet received is not stranded (8.101828ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ [
|
||||
+ {
|
||||
+ at: null,
|
||||
+ from: 'human',
|
||||
+ id: 'd1ba4678-de20-4fd2-9960-f11e3402fa96',
|
||||
+ run: null
|
||||
+ }
|
||||
+ ]
|
||||
- []
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:239: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: [ { id: 'd1ba4678-de20-4fd2-9960-f11e3402fa96', from: 'human', run: null, at: null } ],
|
||||
expected: [],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (12.723555ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (11.737396ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (21.258373ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (8.669373ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (10.640859ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (10.816052ms)
|
||||
✖ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (10.132138ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (10.294567ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (55.024328ms)
|
||||
✔ agents: stranded is per role and per business (19.672162ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (6.94095ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 229.566554
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/agents.test.mjs:164:1
|
||||
✖ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (10.132138ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
[
|
||||
+ [Object: null prototype] {
|
||||
- {
|
||||
at: '2026-10-13T15:17:47.733Z',
|
||||
from: 'human',
|
||||
id: '6b332228-ca7e-40a9-aa49-16db244b286e',
|
||||
run: 'pm-one'
|
||||
}
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/packages/bus/tests/agents.test.mjs:174: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: [ { id: '6b332228-ca7e-40a9-aa49-16db244b286e', from: 'human', run: 'pm-one', at: '2026-10-13T15:17:47.733Z' } ],
|
||||
expected: [ { id: '6b332228-ca7e-40a9-aa49-16db244b286e', from: 'human', run: 'pm-one', at: '2026-10-13T15:17:47.733Z' } ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (13.942874ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (11.905169ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (19.998171ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (8.336331ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (10.106026ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (10.023587ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (11.361653ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (11.728777ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (53.410722ms)
|
||||
✔ agents: stranded is per role and per business (20.417905ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (7.116475ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 11
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 226.513069
|
||||
@@ -0,0 +1,128 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (34.073634ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (39.465959ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (51.354449ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (32.238543ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (34.505514ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (31.087613ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (27.867258ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (23.738696ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (89.958166ms)
|
||||
✔ agents: stranded is per role and per business (31.358283ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (10.699473ms)
|
||||
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (43.717544ms)
|
||||
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (44.842492ms)
|
||||
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (39.485038ms)
|
||||
✔ launch events require a human CLI capability; generic emit cannot forge authority events (27.518645ms)
|
||||
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (35.804066ms)
|
||||
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (30.112643ms)
|
||||
✔ task action subjects and linked decision trail are complete and ordered (35.827416ms)
|
||||
✔ launch binding is durable and reconnecting requires the identical trusted record (14.750731ms)
|
||||
✔ business isolation includes inherited object names and cross-business message references (23.69314ms)
|
||||
✔ authority never transfers between action, run, target, unresolved or replaced role holder (27.740481ms)
|
||||
✔ task projection uses schema current view, skipping earlier and equal-start polls (14.924398ms)
|
||||
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (21.957727ms)
|
||||
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (13.48611ms)
|
||||
✔ both arbiters require human resolution when their cross-role route is themselves (23.097114ms)
|
||||
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (17.927831ms)
|
||||
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.170563ms)
|
||||
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (30.751658ms)
|
||||
✔ credentials refuses a role session and any argument (31.339731ms)
|
||||
✔ credentials reads the status on each request, and a broker without one lists none (34.999906ms)
|
||||
✔ the broker process answers credentials without a token, path or variable in its output, log or store (111.440454ms)
|
||||
✔ only validated broker references load; returned data and exceptions cannot expose a known token (6.004324ms)
|
||||
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (4.182551ms)
|
||||
✔ expiry refuses use and env references never become client data (2.818922ms)
|
||||
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (2.933681ms)
|
||||
✔ opaque tokens shorter than 16 characters refuse before use (0.779283ms)
|
||||
✔ roles lists open decisions routed to a role, and never the human inbox (56.99301ms)
|
||||
✔ recent lists closed decisions of every route, newest close first, with how each closed (49.087025ms)
|
||||
✔ recent gives the closing event as closed when the decision was seen first (53.004206ms)
|
||||
✔ recent stops at RECENT rows (129.897969ms)
|
||||
✔ every row is what trail already gives the same session for that id (39.986653ms)
|
||||
✔ a role session can't list decisions, so it can't read another role's open decisions (38.405307ms)
|
||||
✔ decisions is a read: one business, strict arguments, nothing written (38.540733ms)
|
||||
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (35.937528ms)
|
||||
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (20.090794ms)
|
||||
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (21.445186ms)
|
||||
✔ endLaunch leaves a claim another run took alone (23.612146ms)
|
||||
✔ refuse records action.refused against the caller with the code only (17.91409ms)
|
||||
✔ launches off refuses role.launch with launch-revoked until launches on (36.082339ms)
|
||||
✔ broker process: launch ops authorize role.launch, record refusals and end runs (119.334471ms)
|
||||
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (4.086167ms)
|
||||
✔ process reader gets own kernel identity without exposing environment values (0.849918ms)
|
||||
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.024526ms)
|
||||
✔ real pid 1 remains inspectable when its environment is protected (0.394133ms)
|
||||
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (41.210145ms)
|
||||
✔ missing and foreign-business citations refuse and roll back message and grant (29.378314ms)
|
||||
✔ cross-role sends still need a matching resolved decision and consume it once (37.133284ms)
|
||||
✔ messages pages all human messages newest first without writes; read state is history (407.709662ms)
|
||||
✔ messages refuses role callers, invalid paging and cursors outside this human feed (18.307523ms)
|
||||
✔ views messages passes only its paging arguments through (0.256451ms)
|
||||
✔ messages isolates businesses even with a valid foreign cursor (10.756823ms)
|
||||
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (106.022277ms)
|
||||
✔ startup token refusal returns safe code without value or partial listening broker (66.86548ms)
|
||||
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (82.660338ms)
|
||||
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (99.130862ms)
|
||||
✔ trusted host registers later launches; socket clients never have a registration verb (57.577544ms)
|
||||
✔ runtime excludes declared project roots even when host supplies no repoRoots (33.853718ms)
|
||||
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (47.773636ms)
|
||||
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (56.518817ms)
|
||||
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (27.676894ms)
|
||||
✔ a message with no reply gives an empty list (20.53781ms)
|
||||
✔ replies come oldest first and stop at REPLIES (117.277147ms)
|
||||
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (36.986411ms)
|
||||
✔ strict arguments; a role session gets human-required (36.255658ms)
|
||||
✔ views passes replies through as one call with the id (0.225321ms)
|
||||
✔ requirements returns the committed list to a holding role session, and no one else (44.380541ms)
|
||||
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (21.770025ms)
|
||||
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (24.740727ms)
|
||||
✔ v3b prototype refusals, views and append-only mutations (142.177976ms)
|
||||
✔ gated approval authorizes once, survives store reopen, and fresh approval works (56.625347ms)
|
||||
✔ another run cannot consume an approval; a failed check leaves it usable (30.609873ms)
|
||||
✔ two scheduled callers have exactly one grant and one consumed refusal (25.317923ms)
|
||||
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (57.702095ms)
|
||||
✔ class drift gated to cross-role refuses before consumption (33.622128ms)
|
||||
✔ class drift cross-role to gated refuses before consumption (29.691958ms)
|
||||
✔ class drift gated to within-role refuses before consumption (25.686053ms)
|
||||
✔ class drift cross-role to within-role refuses before consumption (26.408431ms)
|
||||
✔ class drift within-role to gated refuses before consumption (20.525672ms)
|
||||
✔ class drift within-role to cross-role refuses before consumption (19.460343ms)
|
||||
✔ message.send consumes approval and prevents a later send or authorize (20.627253ms)
|
||||
✔ role.revoke consumes approval and prevents a later revoke or authorize (23.925099ms)
|
||||
✔ creates private WAL store and excludes a second writer until explicit close (25.362527ms)
|
||||
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (21.64732ms)
|
||||
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (26.383741ms)
|
||||
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (78.004669ms)
|
||||
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (14.923493ms)
|
||||
✔ async transactions refuse before invoking their function (11.500946ms)
|
||||
✔ the session default fits under the reply cap and pages to the end with next (204.10295ms)
|
||||
✔ the short form: no description, assignees by role, every listed key (80.89072ms)
|
||||
✔ each filter, alone and together, and total counts every match (150.663985ms)
|
||||
✔ task_ref reads one task in full, as the whole board has it (86.325904ms)
|
||||
✔ no arguments is the read the Console and mosaic make, unchanged (69.683812ms)
|
||||
✔ an unknown argument or a bad value refuses invalid-request (283.367667ms)
|
||||
✔ tasks rows carry priority and requirement for every session that reads tasks (54.947013ms)
|
||||
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (80.716807ms)
|
||||
✔ an empty business and one with snapshots but no task.created read cleanly (15.6107ms)
|
||||
✔ recordTask keeps sync reads and a role write apart (27.818744ms)
|
||||
✔ read_at must be one canonical UTC format, so the projection compares strings safely (13.184118ms)
|
||||
✔ a bad entry refuses the whole record (15.203276ms)
|
||||
✔ taskView reads the projection for one business (14.860047ms)
|
||||
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (24.923573ms)
|
||||
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (274.44252ms)
|
||||
✔ without an adapter the server refuses every task verb (15.577177ms)
|
||||
✔ the runtime refuses an invalid adapter and closes a valid one (16.504518ms)
|
||||
✔ the process loads the S3 adapter from plain-data trackers (97.764485ms)
|
||||
✔ socket capability stamps launch identity; shared views use wire, no SQL client (35.303868ms)
|
||||
✔ two wire claims serialize; a lost reply never automatically retries (26.191552ms)
|
||||
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (13.276468ms)
|
||||
✔ client preserves UTF-8 when a response divides a multibyte character (12.570749ms)
|
||||
✔ committed mutation followed by dropped reply reports unknown and is never retried (15.830939ms)
|
||||
ℹ tests 120
|
||||
ℹ suites 0
|
||||
ℹ pass 120
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1061.192325
|
||||
@@ -0,0 +1,68 @@
|
||||
✔ config directory and file path follow MOSAIC_CONFIG (1.105521ms)
|
||||
✔ the fixture business validates and comes back frozen (3.318205ms)
|
||||
✔ two instances may share a definition (1.066025ms)
|
||||
✔ top-level refusals (3.357815ms)
|
||||
✔ arbiters and projects (4.762094ms)
|
||||
✔ role instances (2.176912ms)
|
||||
✔ Vikunja bots (4.831908ms)
|
||||
✔ a role without Vikunja takes no tracker block (1.74643ms)
|
||||
✔ credential references match the definition's services (2.11184ms)
|
||||
✔ launch (6.154141ms)
|
||||
✔ loadBusiness: file checks (1.447703ms)
|
||||
✔ loadBusiness: not a regular file (33.806339ms)
|
||||
✔ loading writes nothing (0.988838ms)
|
||||
✔ names that are Object.prototype properties don't count as declared (2.492648ms)
|
||||
✔ the shipped example refuses as written and validates once filled in (0.539811ms)
|
||||
✔ usage errors exit 4 (267.935284ms)
|
||||
✔ validate: a good business exits 0 and prints instance digests (60.126215ms)
|
||||
✔ validate: project files (309.619804ms)
|
||||
✔ validate: missing files and a broken system config (229.807437ms)
|
||||
✔ validate: credential reference problems exit 2 and name each one (60.420875ms)
|
||||
✔ validate: a token file inside the repository is refused (61.741521ms)
|
||||
✔ validate: role definitions come from MOSAIC_ROLES_DIR (186.172365ms)
|
||||
✔ resolve: prints one instance's record (185.562149ms)
|
||||
✔ resolve: refusals (366.905383ms)
|
||||
✔ parse: exactly one of file or env, plus the service's date (1.963119ms)
|
||||
✔ check: a good file has no problems (0.618822ms)
|
||||
✔ check never opens the file: a write-only token passes (0.254182ms)
|
||||
✔ check: file problems (0.677371ms)
|
||||
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (0.619808ms)
|
||||
✔ check: dates and environment references (0.28126ms)
|
||||
✔ path and load (1.952815ms)
|
||||
✔ refusals (1.183314ms)
|
||||
✔ systemVars flattens the validated config (1.450204ms)
|
||||
✔ precedence: system, business, project, project role, agent (3.787828ms)
|
||||
✔ limits narrow the definition and never widen it (1.789644ms)
|
||||
✔ role.launch stays within-role only for the instance the launch block names (3.36375ms)
|
||||
✔ limits.authority without role.launch leaves the launcher with no launch block (1.357914ms)
|
||||
✔ limits.authority narrows cross-role actions too (0.723386ms)
|
||||
✔ classify (0.869841ms)
|
||||
✔ the record carries what the broker and launcher need (0.755919ms)
|
||||
✔ digest: key order doesn't matter, any value change does (13.197325ms)
|
||||
✔ refusals (1.847902ms)
|
||||
✔ the four shipped version 2 roles load (2.495789ms)
|
||||
✔ shipped role scopes match addendum B section 2 and the SR runbook (0.72726ms)
|
||||
✔ shipped authority follows the note's table (0.430875ms)
|
||||
✔ version 1 files keep loading with no authority (0.752293ms)
|
||||
✔ the conductor policy isn't a role (0.16471ms)
|
||||
✔ a missing role file is exit 4, a symbolic link too (0.306591ms)
|
||||
✔ version 2 refusals (1.008616ms)
|
||||
✔ authority: closed vocabulary, no gated-only action, no overlap (1.588851ms)
|
||||
✔ credentials: Gitea scopes (0.682813ms)
|
||||
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (0.921059ms)
|
||||
✔ credentials: services (0.417434ms)
|
||||
✔ contract: a non-empty regular Markdown file beside the role file (0.526825ms)
|
||||
✔ every key names known layers and a merge rule (0.742074ms)
|
||||
✔ unknown keys and wrong layers refuse (0.596609ms)
|
||||
✔ types (1.375549ms)
|
||||
✔ merge: defaults, then the most specific layer wins (0.209792ms)
|
||||
✔ merge: limits only narrow, and provenance lists each source (0.316785ms)
|
||||
✔ merge doesn't change its inputs (0.132385ms)
|
||||
ℹ tests 60
|
||||
ℹ suites 0
|
||||
ℹ pass 60
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1790.014261
|
||||
@@ -0,0 +1,141 @@
|
||||
✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (1265.579268ms)
|
||||
✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (67.186993ms)
|
||||
✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (144.182087ms)
|
||||
✔ NO_COLOR: no escape codes, except reverse video on the selected row (208.567287ms)
|
||||
✔ 1-9 shows the decide flow's own lines and question; only y resolves (45.649191ms)
|
||||
✔ the app shows the same decide text as mosaic decide (19.827193ms)
|
||||
✔ any key but y or Y at the question answers No; nothing is sent (181.34658ms)
|
||||
✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (59.627031ms)
|
||||
✔ under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10) (233.248028ms)
|
||||
✔ a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12) (29.389913ms)
|
||||
✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (19.796185ms)
|
||||
✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (389.10762ms)
|
||||
✔ quitting at the question answers No and sends nothing, armed or not (30.067688ms)
|
||||
✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (30.110909ms)
|
||||
✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (43.317628ms)
|
||||
✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (282.890597ms)
|
||||
✔ at every size the decide box shows each line y depends on, or says it is hidden (8010.288446ms)
|
||||
✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (65.020898ms)
|
||||
✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (28.851687ms)
|
||||
✔ help or a detail box cut to fit keeps its last line, Esc included (Dewey) (14.614477ms)
|
||||
✔ keys: views, moving, trail of a selection, the command line, help and quit (75.188057ms)
|
||||
✔ one option: the status line says Press 1, not 1-1 (15.157831ms)
|
||||
✔ no business: the app says so and :business picks one (9.038723ms)
|
||||
✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (1.720743ms)
|
||||
✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (171.182575ms)
|
||||
✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.445278ms)
|
||||
✔ decide with --note still asks; a No sends nothing (9.568365ms)
|
||||
✔ the app's business comes from a live bus host only (0.975697ms)
|
||||
✔ runApp leaves the terminal as it found it on q, end of input, SIGTERM, SIGINT, SIGHUP and an error, the first frame's included (6.963681ms)
|
||||
✔ draw is pure: same state, same frame (0.790655ms)
|
||||
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (24.893961ms)
|
||||
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (19.679905ms)
|
||||
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (16.028893ms)
|
||||
✔ decide prints a declining choice as declining (15.624645ms)
|
||||
✔ an unknown outcome is reported once and never resent (13.074712ms)
|
||||
✔ a decision closed before the answer arrives exits 2 and points at its trail (12.880733ms)
|
||||
✔ a prefix that matches two open decisions exits 2 and resolves neither (13.729408ms)
|
||||
✔ without --business a command uses the live host's business, and a stale host.json is not a host (14.381117ms)
|
||||
✔ every human command refuses inside an agent run before it touches the bus (22.600527ms)
|
||||
✔ usage errors exit 4; no business and no host is a usage error (21.573157ms)
|
||||
✔ agents and tasks print through the broker (13.911804ms)
|
||||
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (3.104624ms)
|
||||
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (55.696749ms)
|
||||
✔ trackers come from the tracker.* variables of the one project that names a tracker project (88.56858ms)
|
||||
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (45.978803ms)
|
||||
✔ two projects that each name a tracker project refuse, since the boot shape holds one (58.284977ms)
|
||||
✔ a business without tracker.baseUrl gets no trackers entry (39.194791ms)
|
||||
✔ an unknown business and a broken system config refuse with exit 3 (58.161518ms)
|
||||
✔ follow prints an unsolicited message after plain talk returned (35.102903ms)
|
||||
✔ talk --follow continues after reply and prints a later unsolicited message (32.362242ms)
|
||||
✔ inbox lists all unread human messages under decisions, does not deliver or read them (290.404032ms)
|
||||
✔ follow refuses agent context and bad arguments (8.689102ms)
|
||||
✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (7.994932ms)
|
||||
✔ plain talk leaves the process SIGINT handlers unchanged (9.611497ms)
|
||||
✔ empty views say so (0.9434ms)
|
||||
✔ the trail keeps the broker's order and names a decision's task without its rows (1.315562ms)
|
||||
✔ tasks print the tracker fields the snapshot carries (0.221558ms)
|
||||
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (896.966462ms)
|
||||
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (140.092444ms)
|
||||
✔ a second host for the same data root refuses with exit 3 while the first runs (78.503552ms)
|
||||
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1081.445402ms)
|
||||
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (141.681287ms)
|
||||
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (70.194863ms)
|
||||
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (121.236847ms)
|
||||
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (118.368267ms)
|
||||
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (71.236324ms)
|
||||
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (120.97663ms)
|
||||
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (78.542584ms)
|
||||
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (132.797956ms)
|
||||
✔ watchChildren reports a child that died before it was called, and one that dies later (21.026051ms)
|
||||
✔ bus stop refuses to signal a live pid that is not a bus host (202.315724ms)
|
||||
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (253.479425ms)
|
||||
✔ bus start refuses with exit 3 without a notifier config (106.391234ms)
|
||||
✔ bus start runs until bus stop; status reports it while it runs (637.589589ms)
|
||||
✔ bus-service.sh renders the unit and installs it into a given directory (43.21602ms)
|
||||
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (55.06629ms)
|
||||
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (11.905229ms)
|
||||
✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (7.685124ms)
|
||||
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1255.436873ms)
|
||||
✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (492.110795ms)
|
||||
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or kept and logged (3467.256509ms)
|
||||
✔ a project clone that can't be made refuses checkout-failed, from --pm and from a session, with no run and no clone left (109.268639ms)
|
||||
✔ a runner that stops at once ends its launch with the runner's reason (173.429681ms)
|
||||
✔ a Claude Code launch with no session credential refuses before it starts (84.161832ms)
|
||||
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (160.62623ms)
|
||||
✔ a runner killed from the host ends its launch as killed, not failed (226.688959ms)
|
||||
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (88.970462ms)
|
||||
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1029.399538ms)
|
||||
✔ 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 (569.445101ms)
|
||||
✔ 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 (3534.995888ms)
|
||||
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (80.226745ms)
|
||||
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (82.311723ms)
|
||||
✔ a launch client that never closes its side doesn't hold the host's close (85.254371ms)
|
||||
✔ a runner that ignores SIGTERM is killed when the host closes (1196.396688ms)
|
||||
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (4.373715ms)
|
||||
✔ zoned uses the IANA zone across DST (21.077771ms)
|
||||
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (25.834096ms)
|
||||
✔ two blocking decisions get two DMs with different nonces (17.880527ms)
|
||||
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.309125ms)
|
||||
✔ a failed DM is journaled, backs off, and is retried until it lands (17.480147ms)
|
||||
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (20.965472ms)
|
||||
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (21.386645ms)
|
||||
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (15.23777ms)
|
||||
✔ 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 (120.049165ms)
|
||||
✔ a restart after the second refusal does not send before that refusal's 30 min are up (41.800628ms)
|
||||
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (10.097953ms)
|
||||
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (8.180453ms)
|
||||
✔ an inbox read failure is logged and the next poll retries (0.718422ms)
|
||||
✔ no Discord id reaches the journal or the log (9.549512ms)
|
||||
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.044728ms)
|
||||
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.182204ms)
|
||||
✔ the journal: a whole file that is one torn line truncates to empty (1.627964ms)
|
||||
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.48609ms)
|
||||
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.60709ms)
|
||||
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.021938ms)
|
||||
✔ the journal: a symlinked directory refuses and says it is a link (0.340478ms)
|
||||
✔ 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.489015ms)
|
||||
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.394826ms)
|
||||
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.440278ms)
|
||||
✔ digest content stays within Discord's 2000 characters (0.276851ms)
|
||||
✔ runLoop never overlaps ticks and stops after the one in flight (111.180356ms)
|
||||
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 (317.235756ms)
|
||||
✔ the transport writes {business, verb, args} to the child and reads its JSON (39.927236ms)
|
||||
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2220.600617ms)
|
||||
✔ busExit and refuseInsideAgent (0.51107ms)
|
||||
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (51.769236ms)
|
||||
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1016.561095ms)
|
||||
✔ a late reply the Console has listed still reaches the next talk (2032.121912ms)
|
||||
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (9.794877ms)
|
||||
✔ launches off and on go to the broker and change the business's launch state (7.955279ms)
|
||||
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (7.832957ms)
|
||||
ℹ tests 131
|
||||
ℹ suites 0
|
||||
ℹ pass 131
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 12770.184737
|
||||
@@ -0,0 +1,132 @@
|
||||
✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (3.462512ms)
|
||||
✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.239783ms)
|
||||
✔ completed smoke replies and ordinary questions are idle, not human blockers (0.724908ms)
|
||||
✔ only an explicit first-line input request makes a finished reply waiting (0.146107ms)
|
||||
✔ tool activity, user text, errors and unfinished turns override attention text (0.121836ms)
|
||||
✔ STOP access failure is unknown, not absence, under a non-root identity (25.70639ms)
|
||||
✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (2.475764ms)
|
||||
✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (1.163762ms)
|
||||
✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (4.7441ms)
|
||||
✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.509134ms)
|
||||
✔ server rescans connector discovery and refuses HTTP reply without transport (29.578281ms)
|
||||
✔ connector session links and linked directories are not read (1.046731ms)
|
||||
✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (2.450976ms)
|
||||
✔ CLI print uses the relaunch notice instead of old current preview (55.593283ms)
|
||||
✔ connector owner and fixed task never inherit a native relaunch notice (1.885442ms)
|
||||
✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (0.967395ms)
|
||||
✔ loadConfig: missing file throws ConfigError (1.369011ms)
|
||||
✔ loadConfig: invalid JSON throws ConfigError (0.276245ms)
|
||||
✔ loadConfig: missing dataRoot throws ConfigError (0.20175ms)
|
||||
✔ loadConfig: relative dataRoot throws ConfigError (0.19657ms)
|
||||
✔ loadConfig: valid config returns dataRoot (0.752192ms)
|
||||
✔ findNewestSession: picks the newest by mtime among two files (0.419117ms)
|
||||
✔ findNewestSession: finds files in nested subdirectories (0.277914ms)
|
||||
✔ findNewestSession: returns null for a missing dir (0.109761ms)
|
||||
✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.601617ms)
|
||||
✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.12779ms)
|
||||
✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.46219ms)
|
||||
✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (0.994066ms)
|
||||
✔ deriveState: full state table (0.159092ms)
|
||||
✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.293103ms)
|
||||
✔ rule: newest entry is a tool result with no assistant text after it is working (0.256677ms)
|
||||
✔ rule: a finished ordinary turn is idle, even if it says your move (0.238945ms)
|
||||
✔ task: the first user message of the session, from text blocks (0.243874ms)
|
||||
✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.234898ms)
|
||||
✔ task: no user message in the log means null (shown as unknown), never a guess (0.247838ms)
|
||||
✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.340358ms)
|
||||
✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.46937ms)
|
||||
✔ scan: the written record carries task, workspace and activeProject (0.424461ms)
|
||||
✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.511057ms)
|
||||
✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.355025ms)
|
||||
✔ registration: a record whose pid is gone is stale; derived values win, sources say derived, registered stays with alive false; a pid the probe cannot decide is not stale; pidAlive itself (0.861736ms)
|
||||
✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.380305ms)
|
||||
✔ loadRegistrations: a missing seatsDir gives empty lists (0.139476ms)
|
||||
✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (0.66136ms)
|
||||
✔ 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.284284ms)
|
||||
✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (0.611224ms)
|
||||
✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.282498ms)
|
||||
✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.421769ms)
|
||||
✔ scanAgent: ageSeconds is computed from the injected now (0.229199ms)
|
||||
✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.135143ms)
|
||||
✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.321351ms)
|
||||
✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.392299ms)
|
||||
✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (0.766827ms)
|
||||
✔ scan: relative boardDir throws ConfigError (0.076061ms)
|
||||
✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (51.129199ms)
|
||||
✔ CLI: missing config exits 2 with a refused: message (47.727488ms)
|
||||
✔ CLI: unknown command exits 2 (47.883708ms)
|
||||
✔ CLI: unknown --liveness value exits 2 (48.23578ms)
|
||||
✔ panesRunPi: true when any trimmed line equals 'pi' (0.2978ms)
|
||||
✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.104799ms)
|
||||
✔ tmuxIsAlive: a pane running pi is alive (0.22103ms)
|
||||
✔ tmuxIsAlive: session exists but pi has exited is not alive (0.089566ms)
|
||||
✔ tmuxIsAlive: no such tmux session is not alive (0.065728ms)
|
||||
✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.085151ms)
|
||||
✔ tmuxIsAlive: passes -L <socket> only when a socket is given (0.127259ms)
|
||||
✔ parsePanes: one pane per line, command and optional tab-separated path (0.115255ms)
|
||||
✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.094308ms)
|
||||
✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.128674ms)
|
||||
✔ loadSeen: missing file returns {} (0.186971ms)
|
||||
✔ loadSeen: invalid JSON throws ConfigError (0.243003ms)
|
||||
✔ loadSeen: a JSON array throws ConfigError (0.207626ms)
|
||||
✔ loadSeen: a non-string value throws ConfigError (0.228098ms)
|
||||
✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.415303ms)
|
||||
✔ markSeen: seen false deletes the key (0.328459ms)
|
||||
✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.269807ms)
|
||||
✔ markSeen: project containing '/' throws ConfigError (0.145128ms)
|
||||
✔ markSeen: non-boolean seen throws ConfigError (0.138934ms)
|
||||
✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.377236ms)
|
||||
✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.30635ms)
|
||||
✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.299076ms)
|
||||
✔ scanAgent: an error-state session with a matching mark is seen (0.308967ms)
|
||||
✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.66311ms)
|
||||
✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.271272ms)
|
||||
✔ 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.862758ms)
|
||||
✔ 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.78407ms)
|
||||
✔ 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.785439ms)
|
||||
✔ isLoopbackHost: recognizes loopback hosts (1.152353ms)
|
||||
✔ isLoopbackHost: rejects non-loopback hosts (3.89857ms)
|
||||
✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (2.748706ms)
|
||||
✔ startServer: serves page, healthz, and a rescanning /api/board (28.889627ms)
|
||||
✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (5.377742ms)
|
||||
✔ startServer: /api/board returns 500 JSON with an error field when scan throws (2.098377ms)
|
||||
✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (51.243259ms)
|
||||
✔ CLI: serve rejects a non-numeric --port with exit 2 (47.846871ms)
|
||||
✔ CLI: scan still works after the async cli refactor (60.910632ms)
|
||||
✔ CLI: live serve prints its URL and answers /healthz (69.704882ms)
|
||||
✔ page.html: esc() escapes every HTML-significant character (0.673282ms)
|
||||
✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (8.658451ms)
|
||||
✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (3.234982ms)
|
||||
✔ POST /api/seen with invalid JSON returns 400 (1.603714ms)
|
||||
✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (1.975787ms)
|
||||
✔ POST /api/seen with a missing agent returns 400 (1.308636ms)
|
||||
✔ POST /api/board returns 405; PUT /api/seen returns 405 (1.639763ms)
|
||||
✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (55.580484ms)
|
||||
✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.307759ms)
|
||||
✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.255742ms)
|
||||
✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.156363ms)
|
||||
✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.137229ms)
|
||||
✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.286411ms)
|
||||
✔ 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.553952ms)
|
||||
✔ POST /api/reply: runs agent-send.sh with -s from the registration, -S <host>:control-board, -m text plus the fixed trailer, no -L on the default socket, MOSAIC_TMUX_SOCKET stripped; answers delivered with the exit code and both streams (26.402292ms)
|
||||
✔ POST /api/reply: a registration with a tmux socket adds -L <socket> (24.068721ms)
|
||||
✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (28.658539ms)
|
||||
✔ 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 (15.183644ms)
|
||||
✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (4.623164ms)
|
||||
✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.167406ms)
|
||||
✔ 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.573167ms)
|
||||
✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (2.458634ms)
|
||||
✔ 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.333717ms)
|
||||
✔ 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 (8.211155ms)
|
||||
✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (27.32202ms)
|
||||
✔ 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.678278ms)
|
||||
✔ every refusal code the reader can raise has an HTTP status (1.010148ms)
|
||||
✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (47.746908ms)
|
||||
ℹ tests 124
|
||||
ℹ suites 0
|
||||
ℹ pass 124
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 628.581015
|
||||
@@ -0,0 +1,190 @@
|
||||
✔ W1: two processes acquire the same pair at once; exactly one claim (133.749727ms)
|
||||
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (7.517198ms)
|
||||
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (4.188687ms)
|
||||
✔ W2: acquire while a claim is reserved or active refuses already-active (130.211808ms)
|
||||
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (181.565577ms)
|
||||
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (150.912079ms)
|
||||
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (17.055662ms)
|
||||
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (16.593456ms)
|
||||
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.77561ms)
|
||||
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (5659.941198ms)
|
||||
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (24230.667799ms)
|
||||
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (140.521458ms)
|
||||
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (90.754909ms)
|
||||
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (251.375408ms)
|
||||
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (184.759229ms)
|
||||
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (196.474291ms)
|
||||
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (35.433685ms)
|
||||
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (101.273738ms)
|
||||
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (31.826969ms)
|
||||
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (86.748721ms)
|
||||
✔ W11: the controller writes no session file; only the fake engine's own appends appear (21.648436ms)
|
||||
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (49.436052ms)
|
||||
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (54.316059ms)
|
||||
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.311402ms)
|
||||
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.170358ms)
|
||||
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.745731ms)
|
||||
✔ no shim from this file's tests is left running for the after hook (4.73106ms)
|
||||
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (317.351946ms)
|
||||
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (164.644059ms)
|
||||
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (288.219338ms)
|
||||
✔ K4: two engines; force stop one; the other survives by independent observation (2342.565236ms)
|
||||
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (166.926464ms)
|
||||
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (268.318369ms)
|
||||
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (206.13767ms)
|
||||
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4394.727466ms)
|
||||
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (439.678059ms)
|
||||
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (360.368897ms)
|
||||
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (231.707864ms)
|
||||
✔ K6: recover without proof, without confirmation, or with changed pins is refused (62.041576ms)
|
||||
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (31.080769ms)
|
||||
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (40.895407ms)
|
||||
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3031.495939ms)
|
||||
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (5.491132ms)
|
||||
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (33.839098ms)
|
||||
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (23.356317ms)
|
||||
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (85.091393ms)
|
||||
✔ R1: after a proven force stop the controller releases the scope; the shim and the unit are gone (178.469847ms)
|
||||
✔ R2: close releases the scope of a binding proven stopped, once, when the stop's own release hasn't run (271.498601ms)
|
||||
✔ R3: no release after an unavailable stop or an unverified proof: the scope and its shim stay as evidence (699.970209ms)
|
||||
✔ E1: a natural engine exit with no member left: an engine-exit stop, stopped on a proof naming the engine, and the scope released (185.547387ms)
|
||||
✔ E2: an engine exit with a tool child alive: the stop ends uncertain and nothing is released; a force stop then ends it (2255.997438ms)
|
||||
✔ E3: an engine exit and a force stop take one escalation slot, in either order (615.311679ms)
|
||||
✔ E4: a shim that doesn't answer: the engine-exit stop misses its deadline, ends uncertain and frees the slot (683.405467ms)
|
||||
✔ E5: an engine exit after a force stop ended uncertain starts no engine-exit stop; the force stop stays current (2426.186759ms)
|
||||
✔ E6: the engine-exit observation refuses a reaped process that isn't the recorded engine: another PID or start ticks (186.341177ms)
|
||||
✔ 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) (370.727236ms)
|
||||
✔ R6: releaseCohort releases nothing while the engine runs, for another invocation ID, or for a cohort without a scope (126.635218ms)
|
||||
✔ R7: controller killed after the claim is stopped, before the release: the restart releases the scope (2314.853001ms)
|
||||
✔ R8: controller killed between the two keys' stopped revisions: the restart releases the scope (228.783647ms)
|
||||
✔ R9: a release that ended unavailable is tried again on a confirmed recover, not a refused one (198.411808ms)
|
||||
✔ R10: close releases and signals nothing for a stopped binding whose proof no longer verifies (Filbert N3 on #1536) (170.224763ms)
|
||||
✔ R11: close({ killEngine: true }) after a proven stop sends no signal to the recorded engine PID; a restart releases nothing more (2253.329297ms)
|
||||
✔ R12: a restart onto a claim stopped on a boot proof releases nothing, though its scope is listed (166.416377ms)
|
||||
✔ no shim from this file's tests is left running for the after hook (3.670214ms)
|
||||
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (51.859969ms)
|
||||
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (34.267825ms)
|
||||
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (22.391241ms)
|
||||
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (36.904901ms)
|
||||
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (20.747476ms)
|
||||
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.56442ms)
|
||||
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (21.28511ms)
|
||||
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (123.981029ms)
|
||||
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (32.08413ms)
|
||||
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (19.063098ms)
|
||||
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (32.599652ms)
|
||||
✔ 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 (32.128778ms)
|
||||
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.805851ms)
|
||||
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (26.51974ms)
|
||||
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (44.595331ms)
|
||||
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (39.585796ms)
|
||||
✔ terminal: engine control characters are made visible; a lost connection refuses submit (24.459387ms)
|
||||
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.531085ms)
|
||||
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.325095ms)
|
||||
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.403083ms)
|
||||
✔ 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.573626ms)
|
||||
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.596168ms)
|
||||
✔ 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.531401ms)
|
||||
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.85226ms)
|
||||
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.454178ms)
|
||||
✔ 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.348388ms)
|
||||
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.12027ms)
|
||||
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (280.449978ms)
|
||||
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (60.3253ms)
|
||||
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (82.74542ms)
|
||||
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (140.030167ms)
|
||||
✔ H4: self-takeover is refused (14.130876ms)
|
||||
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (87.611834ms)
|
||||
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (88.732728ms)
|
||||
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (25.456655ms)
|
||||
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (80.955058ms)
|
||||
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (129.258211ms)
|
||||
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (17.222917ms)
|
||||
✔ H13: a retry with the same request ID and different text is refused (15.250635ms)
|
||||
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (152.083325ms)
|
||||
✔ H15: a revoked connection's command is refused; the revocation fence holds (70.007176ms)
|
||||
✔ H16: a second controller for the same session refuses already-active; the first is untouched (18.199119ms)
|
||||
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (56.588639ms)
|
||||
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (25.177336ms)
|
||||
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (58.553806ms)
|
||||
✔ H18: two prompts before any native output: the second refuses busy; one engine write (13.209982ms)
|
||||
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (117.010386ms)
|
||||
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.642579ms)
|
||||
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (460.044704ms)
|
||||
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (364.340871ms)
|
||||
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (323.474026ms)
|
||||
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2393.285745ms)
|
||||
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (465.493658ms)
|
||||
✔ no shim from this file's tests is left running for the after hook (4.780322ms)
|
||||
✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.419899ms)
|
||||
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.690013ms)
|
||||
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.720534ms)
|
||||
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.474869ms)
|
||||
✔ F1: an unreadable fork is never merged into another branch's history (1.806091ms)
|
||||
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.07129ms)
|
||||
✔ F1: a file whose entries are all unreadable shows a notice per line (0.828469ms)
|
||||
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.241417ms)
|
||||
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (4.022019ms)
|
||||
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (7.660947ms)
|
||||
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (9.721278ms)
|
||||
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (8.870638ms)
|
||||
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (11.689504ms)
|
||||
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (14.634839ms)
|
||||
✔ F8: a file swapped for a symlink after the catalogue is refused (3.130869ms)
|
||||
✔ F9: registrations never add or redirect a root (2.601801ms)
|
||||
✔ F10: a header cwd naming another project is refused (3.756203ms)
|
||||
✔ F11: parentSession renders with a marker and the parent is never opened (1.192552ms)
|
||||
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (6.609936ms)
|
||||
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.791791ms)
|
||||
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.545665ms)
|
||||
✔ F13: compaction is a marker in place, then the retained content (1.046416ms)
|
||||
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (564.071683ms)
|
||||
✔ fragments never cut a surrogate pair and keep an empty string (4.697604ms)
|
||||
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.48973ms)
|
||||
✔ unknown conversations, empty files and non-Pi files refuse (2.200912ms)
|
||||
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.036593ms)
|
||||
✔ a seat directory without search permission refuses that root, not the catalogue (1.837602ms)
|
||||
✔ every page and cursor is a valid CHAT-01 record (825.098779ms)
|
||||
✔ the engine pin holds for the installed package (2.968847ms)
|
||||
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (319.847785ms)
|
||||
✔ 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) (574.204409ms)
|
||||
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (278.411697ms)
|
||||
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (92.043332ms)
|
||||
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (48.888281ms)
|
||||
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (23.344978ms)
|
||||
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.333552ms)
|
||||
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (32.004046ms)
|
||||
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (24.794697ms)
|
||||
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (120.974377ms)
|
||||
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (49.012089ms)
|
||||
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1538.232144ms)
|
||||
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (13.148933ms)
|
||||
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (69.532915ms)
|
||||
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (13.735838ms)
|
||||
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (15.948658ms)
|
||||
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (32.938334ms)
|
||||
✔ N10: fake conformance (32.428676ms)
|
||||
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (30.794657ms)
|
||||
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (18.713082ms)
|
||||
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (66.030491ms)
|
||||
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (26.749034ms)
|
||||
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (26.439722ms)
|
||||
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (21.955936ms)
|
||||
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (12.519497ms)
|
||||
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (29.027698ms)
|
||||
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (110.240113ms)
|
||||
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (134.257143ms)
|
||||
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (85.173285ms)
|
||||
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (14.212119ms)
|
||||
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (12.785475ms)
|
||||
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (13.244488ms)
|
||||
✔ 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 (38.212742ms)
|
||||
✔ 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 (26.161297ms)
|
||||
ℹ tests 182
|
||||
ℹ suites 0
|
||||
ℹ pass 182
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 33657.386694
|
||||
@@ -0,0 +1,186 @@
|
||||
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.997973ms)
|
||||
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (2.233922ms)
|
||||
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.613648ms)
|
||||
✔ approvals: a button approves only on its own request message with the matching custom id (0.471193ms)
|
||||
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (14.623303ms)
|
||||
✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (6.123463ms)
|
||||
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.0793ms)
|
||||
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (1.767755ms)
|
||||
✔ authorize: open channel, listed user (1.75883ms)
|
||||
✔ authorize: wrong guild (0.304894ms)
|
||||
✔ authorize: no guild (DM) (0.297014ms)
|
||||
✔ authorize: unlisted channel (0.183641ms)
|
||||
✔ authorize: unknown channel, no info (0.250983ms)
|
||||
✔ authorize: thread of listed parent (0.181203ms)
|
||||
✔ authorize: thread of unlisted parent (0.249527ms)
|
||||
✔ authorize: text channel that is not a thread and not listed (0.133295ms)
|
||||
✔ authorize: unlisted user (1.174064ms)
|
||||
✔ authorize: no author (0.441646ms)
|
||||
✔ authorize: bot author (listed id, bot flag) (0.217413ms)
|
||||
✔ authorize: system author (0.110517ms)
|
||||
✔ authorize: the bot itself (0.081034ms)
|
||||
✔ authorize: webhook (0.811383ms)
|
||||
✔ authorize: mention channel without mention (0.142958ms)
|
||||
✔ authorize: mention channel with bot mention (0.241755ms)
|
||||
✔ authorize: mention channel with @everyone only (0.197732ms)
|
||||
✔ authorize: mention channel mentioning someone else (0.097186ms)
|
||||
✔ authorize: mention channel, content says @bot but mentions empty (0.086814ms)
|
||||
✔ authorize: private thread under mention channel, mentioned (0.083773ms)
|
||||
✔ authorize: private thread under mention channel, not mentioned (0.066755ms)
|
||||
✔ authorize: thread in another guild per channel info (0.06948ms)
|
||||
✔ authorize: not an object (0.060143ms)
|
||||
✔ authorize: no id (0.062715ms)
|
||||
✔ authorize: oversize content is accepted and flagged (0.065446ms)
|
||||
✔ authorize: exactly the limit is not oversize (0.059975ms)
|
||||
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.450629ms)
|
||||
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.149081ms)
|
||||
✔ binding: a complete binding validates and is frozen (2.632207ms)
|
||||
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.461106ms)
|
||||
✔ binding: empty allowlists refuse (0.420626ms)
|
||||
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.221711ms)
|
||||
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.838064ms)
|
||||
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.383229ms)
|
||||
✔ binding: file must be 0600, regular, not a symlink (1.673399ms)
|
||||
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.583471ms)
|
||||
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (92.885851ms)
|
||||
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.722645ms)
|
||||
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (306.217002ms)
|
||||
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (177.330472ms)
|
||||
✔ cli: run refuses when STOP is present, before any network use (112.299458ms)
|
||||
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.74222ms)
|
||||
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.252313ms)
|
||||
✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (17.498601ms)
|
||||
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (16.192979ms)
|
||||
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.461674ms)
|
||||
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.09216ms)
|
||||
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.232593ms)
|
||||
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.113299ms)
|
||||
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.96678ms)
|
||||
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.618044ms)
|
||||
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (0.630637ms)
|
||||
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (0.611189ms)
|
||||
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.318029ms)
|
||||
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.778969ms)
|
||||
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (5.751116ms)
|
||||
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.952948ms)
|
||||
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.639728ms)
|
||||
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (3.171736ms)
|
||||
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.39347ms)
|
||||
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.604335ms)
|
||||
✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (2.511629ms)
|
||||
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.005308ms)
|
||||
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (3.801153ms)
|
||||
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.147757ms)
|
||||
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.256665ms)
|
||||
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.428248ms)
|
||||
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (1.876801ms)
|
||||
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.905927ms)
|
||||
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.195664ms)
|
||||
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.65016ms)
|
||||
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.181257ms)
|
||||
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.69741ms)
|
||||
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.46913ms)
|
||||
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.390645ms)
|
||||
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (49.326188ms)
|
||||
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (31.421473ms)
|
||||
✔ engine: one prompt, one turn, text and usage come back (27.685718ms)
|
||||
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (341.017011ms)
|
||||
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (227.532056ms)
|
||||
✔ engine: timeout sends abort and fails only that turn; the process stays (112.710375ms)
|
||||
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (224.71718ms)
|
||||
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (122.513137ms)
|
||||
✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (211.986133ms)
|
||||
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (613.836385ms)
|
||||
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.290298ms)
|
||||
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.585151ms)
|
||||
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (426.452548ms)
|
||||
✔ engine: a malformed JSONL line fails the turn, not the process (23.544531ms)
|
||||
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.727602ms)
|
||||
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.347621ms)
|
||||
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.52152ms)
|
||||
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.612531ms)
|
||||
✔ gateway: op 9 resumable resumes (0.341192ms)
|
||||
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.838351ms)
|
||||
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.597808ms)
|
||||
✔ gateway: close() is final and unparseable frames are ignored (0.5191ms)
|
||||
✔ git: config validation is strict, needs write: true, a work tree and a private token file (50.875747ms)
|
||||
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (32.578869ms)
|
||||
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (62.780267ms)
|
||||
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.372896ms)
|
||||
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (83.208699ms)
|
||||
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (78.417495ms)
|
||||
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (82.964072ms)
|
||||
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (177.286965ms)
|
||||
✔ git: push pushes the named branch only and reports up to date (59.905896ms)
|
||||
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (78.232009ms)
|
||||
✔ git: the credential helper answers get over https from a private file and nothing else (183.106775ms)
|
||||
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (692.267516ms)
|
||||
✔ lock: the claim is exclusive; a second start against a live owner refuses (4.433759ms)
|
||||
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (60.40899ms)
|
||||
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (0.727633ms)
|
||||
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (2.521198ms)
|
||||
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (33.950387ms)
|
||||
✔ 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 (284.803278ms)
|
||||
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.393736ms)
|
||||
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (21.797493ms)
|
||||
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.946337ms)
|
||||
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (39.904116ms)
|
||||
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (126.130941ms)
|
||||
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (75.445387ms)
|
||||
✔ notices: a kind is recorded per UTC day and found again (0.436345ms)
|
||||
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (4.636733ms)
|
||||
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.206908ms)
|
||||
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (3.340583ms)
|
||||
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (41.689276ms)
|
||||
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (29.0598ms)
|
||||
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (23.045948ms)
|
||||
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (66.247104ms)
|
||||
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (623.329189ms)
|
||||
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.327705ms)
|
||||
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.998187ms)
|
||||
✔ rest: content and nonce limits are enforced locally; typing never throws (0.726751ms)
|
||||
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.799334ms)
|
||||
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.998858ms)
|
||||
✔ setspark config: a bare https or loopback origin, a private key file, a principal (4.011368ms)
|
||||
✔ setspark config: reaches the tools config and the binding as a fixed key (2.246973ms)
|
||||
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.127481ms)
|
||||
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (1.453705ms)
|
||||
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (19.747582ms)
|
||||
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (9.095147ms)
|
||||
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (6.212471ms)
|
||||
✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.148241ms)
|
||||
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.546912ms)
|
||||
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (2.126594ms)
|
||||
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1006.352737ms)
|
||||
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.477566ms)
|
||||
✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (5.941597ms)
|
||||
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.170071ms)
|
||||
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.195608ms)
|
||||
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.241689ms)
|
||||
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.741407ms)
|
||||
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.764442ms)
|
||||
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.728692ms)
|
||||
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (2.97304ms)
|
||||
✔ tools: listing and search caps hold (9.474068ms)
|
||||
✔ tools: credential shapes are caught; ordinary prose and ids are not (0.826829ms)
|
||||
✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (5.087849ms)
|
||||
✔ tools: an unreadable file under the root is skipped by search and refused by read (1.208936ms)
|
||||
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.149117ms)
|
||||
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.229047ms)
|
||||
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.519012ms)
|
||||
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.652192ms)
|
||||
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (2.251996ms)
|
||||
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.476014ms)
|
||||
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1017.917413ms)
|
||||
✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (4.221118ms)
|
||||
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.221526ms)
|
||||
✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (2.53293ms)
|
||||
✔ 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.221904ms)
|
||||
ℹ tests 178
|
||||
ℹ suites 0
|
||||
ℹ pass 178
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 2575.531903
|
||||
@@ -0,0 +1,75 @@
|
||||
✔ sessionModel: agent vars win, then the system's execution settings (9.418652ms)
|
||||
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (6.411451ms)
|
||||
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.699948ms)
|
||||
✔ the prompt's built-in tools line is the harness's own names, as the manifest lists them (8.847901ms)
|
||||
✔ the prompt names the project clones in the workspace, and none when there are none (6.658968ms)
|
||||
✔ a bundle is written once: an existing file refuses (1.439712ms)
|
||||
✔ a path with a single quote can't go into the hook command (1.586678ms)
|
||||
✔ allow exits 0, a deny exits 2 with the reason on stderr (113.887826ms)
|
||||
✔ a missing or wrong policy, or a bad event, exits 2 (74.028273ms)
|
||||
✔ the bundle's wrapped command: a missing gate or node still blocks (1091.938383ms)
|
||||
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (674.9133ms)
|
||||
✔ claude: the hook alone blocks a path outside the workspace (386.430964ms)
|
||||
✔ claude: a second turn resumes the first turn's session (660.133979ms)
|
||||
✔ claude adapter: --restricted is always passed (5.04969ms)
|
||||
✔ claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable (15.400239ms)
|
||||
✔ claude adapter: a directory pin exits 2 before it runs; a pin with a space runs (5.663649ms)
|
||||
✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (677.258577ms)
|
||||
✔ claude: a missing hook or MCP file refuses before claude starts (7.027604ms)
|
||||
✔ pi: policy tools and typed tools pass, anything else is blocked (3.965153ms)
|
||||
✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.962003ms)
|
||||
✔ file tool paths must resolve inside the workspace (1.67041ms)
|
||||
✔ pi's own path normalisation can't be used to step out (1.31408ms)
|
||||
✔ a symlink inside the workspace that points out is outside (1.13196ms)
|
||||
✔ a dangling symlink is refused at any depth, in both harnesses (3.909776ms)
|
||||
✔ read is checked under every spelling pi's read would open, in both harnesses (16.972441ms)
|
||||
✔ other spellings cover directories, dangling links and pi's cwd (1.300839ms)
|
||||
✔ a relative path climbs from the workspace's real path, in both harnesses (4.100057ms)
|
||||
✔ claude path fields per tool (0.80745ms)
|
||||
✔ glob patterns stay inside the workspace (0.883616ms)
|
||||
✔ a path that can't be checked is blocked (0.467075ms)
|
||||
✔ initialize, ping and tools/list (50.636349ms)
|
||||
✔ tools/call goes through the tool socket; a refusal is an isError result (30.679739ms)
|
||||
✔ replies are compact JSON; over the cap a tool error names the size and the cap (33.193605ms)
|
||||
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (27.688652ms)
|
||||
✔ a missing argument is a usage error (27.45272ms)
|
||||
✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (354.121299ms)
|
||||
✔ pi: a reply over the cap is a tool error naming the size and the cap; list_tasks asks for open tasks (306.831116ms)
|
||||
✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (308.901704ms)
|
||||
✔ pi: a read is refused when pi would open another spelling outside (303.826373ms)
|
||||
✔ pi: a relative path climbs from the real path of a workspace behind a symlink (306.29018ms)
|
||||
✔ pi: a relative path climbs from the real path of a dataRoot behind a symlink (303.065497ms)
|
||||
✔ pi: a missing extension refuses before any model call (6.047205ms)
|
||||
✔ pi: an extension without its configuration fails pi's start (246.66592ms)
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (1.493422ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.25407ms)
|
||||
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (161.649338ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (51.280311ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (270.210361ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1187.787398ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (91.940416ms)
|
||||
✔ founder credentials stop before the claim (20) (86.163171ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (92.97959ms)
|
||||
✔ the launch ending under a running session exits 22 (101.409811ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (198.755392ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (50.248734ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (143.39237ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (78.756107ms)
|
||||
✔ the PM gets launch, its task verbs and the reads (7.096043ms)
|
||||
✔ list_requirements reads the requirement ids, for a task.create holder with a tracker (4.976636ms)
|
||||
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.339968ms)
|
||||
✔ a coder with a tracker gets its own task tools and no task_reassign (1.473989ms)
|
||||
✔ launch only when the business's launch block names the instance as launcher (1.428511ms)
|
||||
✔ an action outside the instance's authority has no tool (1.405168ms)
|
||||
✔ callTool: one JSON line out, the result back, a refusal rejects (8.050977ms)
|
||||
✔ list_tasks takes the broker's filters and pages; get_task takes a task_ref (2.767036ms)
|
||||
✔ callTool sends list_tasks done false unless the session names done; other tools as given (2.831743ms)
|
||||
✔ replyText: compact JSON up to the cap, an error naming the size and the cap over it (0.626113ms)
|
||||
ℹ tests 67
|
||||
ℹ suites 0
|
||||
ℹ pass 67
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 9875.590115
|
||||
@@ -0,0 +1,86 @@
|
||||
✔ a raw token file, with or without one trailing newline, reaches curl only through the config stream (114.499879ms)
|
||||
✔ the raw path accepts nothing else, and refuses before curl runs (366.244166ms)
|
||||
✔ the file checks still apply on the raw path: mode, symlink, missing, directory (258.955833ms)
|
||||
✔ the raw path base URL has no override (61.182157ms)
|
||||
✔ the JSON path is unchanged, and JSON never falls through to the raw path (228.58798ms)
|
||||
✔ a file that changes between the two reads refuses before curl runs, with or without a body (712.312829ms)
|
||||
✔ the token reaches no child environment, even with an inherited CFG or SHELLOPTS=allexport (653.545141ms)
|
||||
✔ real helper GET HTTP 200 preserves exit 0 without credentials (13.583916ms)
|
||||
✔ real helper POST HTTP 201 preserves exit 0 without credentials (9.922095ms)
|
||||
✔ real helper GET HTTP 403 preserves exit 1 without credentials (9.415612ms)
|
||||
✔ fixture git subjects only, follow-ups and three session kinds (103.681212ms)
|
||||
✔ text and JSON carry same numbers, open and truncated title (158.659303ms)
|
||||
✔ missing credentials exit 2, no-issues never calls API and shows unknown (125.502191ms)
|
||||
✔ empty range gives no rows and zero totals (91.552555ms)
|
||||
✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (107.146474ms)
|
||||
✔ close-only issue included, even median, missing metadata stays unknown (89.19277ms)
|
||||
✔ unique commits but per-issue links count multiple tags once each (101.930583ms)
|
||||
✔ page cap refuses rather than silently undercounting (86.052973ms)
|
||||
✔ bad API payload not JSON refuses (85.010447ms)
|
||||
✔ bad API payload {} refuses (79.810837ms)
|
||||
✔ bad API payload [{"number":1}] refuses (83.25021ms)
|
||||
✔ partial or malformed session log refuses with location, not content (90.942753ms)
|
||||
✔ a U+2028 or U+2029 inside a session string is one line, not a malformed record (136.011619ms)
|
||||
✔ no sessions is an empty table; symlink source refuses (153.092883ms)
|
||||
✔ reads only refactor even when another branch is checked out (98.810095ms)
|
||||
✔ invalid dates, reverse dates and duplicate options refuse (57.613452ms)
|
||||
✔ T3 agent assignments do not count as human in Table 2 (95.323344ms)
|
||||
✔ preamble parsing and issue number boundaries (0.462216ms)
|
||||
✔ T3 header: agent, or board from control-board; anything short of the full header is human (0.139262ms)
|
||||
✔ no closed issues with human messages means undefined ratio, not invented zero (0.189183ms)
|
||||
✔ T3: seat, archived, unmapped and Researcher threads count; imported, deleted and other-project threads do not (187.62101ms)
|
||||
✔ T3: the default path is read from HOME and prints no path line; --no-t3 says so (382.985987ms)
|
||||
✔ T3: a HOME with no database exits 1 and names --no-t3 (84.582034ms)
|
||||
✔ T3: a file that is not a database exits 1 and names --no-t3 (87.838027ms)
|
||||
✔ T3: a seat thread renamed to another seat exits 1 naming thread, title and roles (116.933894ms)
|
||||
✔ T3: an unmapped thread addressed as a seat exits 1 (118.808291ms)
|
||||
✔ T3: a header to another thread id is not cross-checked (109.193081ms)
|
||||
✔ T3: no project, or two, for this root exits 1 (286.942711ms)
|
||||
✔ T3: a removed column exits 1 and names it (96.88235ms)
|
||||
✔ T3: a missing table exits 1 and names it (99.609995ms)
|
||||
✔ T3: a counted row with an unknown role exits 1 without its text (109.15283ms)
|
||||
✔ T3: a counted row with non-text content exits 1 without its text (111.545018ms)
|
||||
✔ T3: a counted row with an unparseable created_at exits 1 without its text (104.486584ms)
|
||||
✔ T3: a missing orchestration_events makes the diagnostic unknown and keeps the counts (199.998072ms)
|
||||
✔ T3: a human message with no event counts in humanWithoutEvent (118.662279ms)
|
||||
✔ T3: an unparseable event makes the diagnostic unknown and keeps the counts (200.105469ms)
|
||||
✔ T3: an event with no string messageId makes the diagnostic unknown and keeps the counts (202.719162ms)
|
||||
✔ T3: an error that is not from SQLite is rethrown, not reported as a database failure (58.06982ms)
|
||||
✔ T3: a symlink at ~/.t3 exits 1 (84.070526ms)
|
||||
✔ T3: a symlink at ~/.t3/userdata exits 1 (87.458727ms)
|
||||
✔ T3: a symlink at ~/.t3/userdata/state.sqlite exits 1 (81.825271ms)
|
||||
✔ T3: with --t3-db, a symlinked file or directory exits 1 (174.536175ms)
|
||||
✔ T3 WAL: the newest message only in -wal, writer attached, is counted (138.300559ms)
|
||||
✔ T3 WAL: stopped cleanly, counts are correct and the main file is unchanged (142.563908ms)
|
||||
✔ T3 WAL: -wal without -shm in a writable directory is read (157.106201ms)
|
||||
✔ T3 WAL: -wal without -shm in a read-only directory exits 1 (137.293417ms)
|
||||
✔ T3 WAL: stopped cleanly in a read-only directory exits 1 (114.052252ms)
|
||||
✔ T3: a lock held past the 5 s busy timeout exits 1 and names --no-t3 (5118.070071ms)
|
||||
✔ a done row whose closing issue is open is a violation; a row that is not done is not (1.776172ms)
|
||||
✔ an issue several rows close is expected closed only once all of them are done (0.48697ms)
|
||||
✔ closure needs positive evidence: unknown is undecided, and so is a skipped or short issue check (0.340916ms)
|
||||
✔ each owner of an in-progress or in-review row gets one liveness class (7.626341ms)
|
||||
✔ a required row not done after 14 days is a violation; a legacy row uses genesis as its lower bound (0.39923ms)
|
||||
✔ an ISO requiredSince, as `set required` writes it, ages from its UTC day; one that does not parse is a violation (0.230852ms)
|
||||
✔ the text section always ends in a count and a result, and never prints a full pass (0.330809ms)
|
||||
✔ pidAlive: a running pid is present, an exited one is gone, and EPERM still means present (24.251264ms)
|
||||
✔ issue states: open list first, then the metric page, then at most 10 lookups (259.743507ms)
|
||||
✔ a full open list: lookups settle what it leaves out, and only an unsettled issue keeps it undecided (341.741522ms)
|
||||
✔ the open list refuses on a failed call or a bad record, and never echoes the helper (163.52812ms)
|
||||
✔ a helper call past the deadline is killed with its child, and the call reports it (2006.947238ms)
|
||||
✔ readQueue loads queue.json through the queue validator and refuses anything else (85.180788ms)
|
||||
✔ protected changes list every in-range entry that changes a required or parked row (229.910542ms)
|
||||
✔ the CLI prints the queue section above the weekly table and under a queue key in --json (469.556962ms)
|
||||
✔ a queue with nothing wrong prints 0 violations and a reduced pass, never a full pass (191.13601ms)
|
||||
✔ --no-issues makes no call and leaves the issue checks undecided; --no-queue skips the section (254.115568ms)
|
||||
✔ the CLI refuses a bad queue before any call, and a failed open list with exit 2 (226.087529ms)
|
||||
✔ --unsupported-runtime repeats once per seat and takes a seat name (286.646616ms)
|
||||
✔ an unreadable config makes every owner invalid instead of passing them (125.698309ms)
|
||||
ℹ tests 78
|
||||
ℹ suites 0
|
||||
ℹ pass 78
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 10713.404558
|
||||
@@ -0,0 +1,77 @@
|
||||
✔ pure resolution selects current default or explicit enrolled account (1.89761ms)
|
||||
✔ scope is explicit, bounded and never inferred (1.582553ms)
|
||||
✔ fork pin is preserved against default change, override, missing account and revocation (0.71801ms)
|
||||
✔ unenrolled account/provider, missing harness, model expansion and native model ceiling refuse (0.788292ms)
|
||||
✔ only explicit synthetic credential forms and internal fixture stores admitted (5.368164ms)
|
||||
✔ two concurrent workspaces of the same agent publish distinct complete private generations (36.392401ms)
|
||||
✔ same execution ID is exclusively claimed and cannot overwrite a published generation (35.193101ms)
|
||||
✔ failed generation after-auth preserves prior files, records failure and refuses blind same-ID retry (49.292621ms)
|
||||
✔ failed generation before-publish preserves prior files, records failure and refuses blind same-ID retry (59.410735ms)
|
||||
✔ credential lock contention refuses without duplicate side effects (11.933736ms)
|
||||
✔ symlinked pre-existing final target is refused and never followed (28.003679ms)
|
||||
✔ invalid registry cannot resolve; no fallback to supplied partial entries (0.291993ms)
|
||||
✔ post-publication failure records uncertainty, preserves complete generation and prevents replay (27.259651ms)
|
||||
✔ expired credentials refresh under transaction and subsequent generation reuses rotation (87.714679ms)
|
||||
✔ refresh failure retains prior generation and store state (64.054275ms)
|
||||
✔ refresh timeout retains prior generation and store state (152.860938ms)
|
||||
✔ refresh malformed retains prior generation and store state (71.934133ms)
|
||||
✔ concurrent refresh on same account refuses contention while unrelated account proceeds (222.335524ms)
|
||||
✔ invalid refresh options refuse before burning claim (35.564838ms)
|
||||
✔ fixed fake process rotates both OAuth fields without mutating caller input (27.853656ms)
|
||||
✔ concurrent isolated processes preserve separate provider credentials (25.39937ms)
|
||||
✔ fake failure is refused with fixed diagnostics (22.655206ms)
|
||||
✔ fake malformed is refused with fixed diagnostics (20.11861ms)
|
||||
✔ fake timeout is refused with fixed diagnostics (103.099568ms)
|
||||
✔ fake unchanged is refused with fixed diagnostics (21.470146ms)
|
||||
✔ caller executable/environment injection is rejected before spawning (0.273501ms)
|
||||
✔ valid fixture tree validates and lists without secrets (69.9726ms)
|
||||
✔ unknown-field refuses (0.379379ms)
|
||||
✔ invalid-id refuses uppercase and traversal shapes (0.279434ms)
|
||||
✔ plain-http baseUrl requires allowInsecureTransport (0.300833ms)
|
||||
✔ native provider rejects allowInsecureTransport (0.134356ms)
|
||||
✔ unsupported credential type and kind refuse (0.144362ms)
|
||||
✔ account provider-path mismatch refuses (0.585174ms)
|
||||
✔ profile account refs must be provider/account shaped (0.22552ms)
|
||||
✔ seat selection accepts fork pin field, validates account refs (0.20722ms)
|
||||
✔ harness manifest id must equal executable (gate 1) (0.200129ms)
|
||||
✔ CLI validate: duplicate provider id across files refuses (26.939995ms)
|
||||
✔ CLI validate: missing referenced provider/account refuse (31.940394ms)
|
||||
✔ CLI validate: broken JSON refuses without secret echo (27.113312ms)
|
||||
✔ CLI usage errors exit 2 (48.786413ms)
|
||||
✔ credential.json sibling presence does not break validation and is never read (60.662778ms)
|
||||
✔ D1 missing, empty and structurally empty roots refuse, no list projection (165.801906ms)
|
||||
✔ D1 required directory auth cannot be absent (51.340859ms)
|
||||
✔ D1 required directory auth/providers cannot be absent (52.763162ms)
|
||||
✔ D1 required directory auth/accounts cannot be absent (59.443207ms)
|
||||
✔ D1 required directory auth/settings cannot be absent (62.608998ms)
|
||||
✔ D1 required directory harnesses cannot be absent (65.413767ms)
|
||||
✔ D1 root file and unreadable metadata refuse (103.005345ms)
|
||||
✔ D2 no symlink traversal at auth/providers/openai-codex.json (57.404043ms)
|
||||
✔ D2 no symlink traversal at auth/accounts/openai-codex/homelab-openai (60.218809ms)
|
||||
✔ D2 no symlink traversal at auth/providers (52.379835ms)
|
||||
✔ D2 no symlink traversal at auth (50.902617ms)
|
||||
✔ D2 root and ancestor symlinks and lexical traversal refuse (147.629298ms)
|
||||
✔ private filesystem modes enforced for root (51.321874ms)
|
||||
✔ private filesystem modes enforced for auth (54.771934ms)
|
||||
✔ private filesystem modes enforced for auth/providers/openai-codex.json (54.662939ms)
|
||||
✔ private filesystem modes enforced for auth/accounts/openai-codex/homelab-openai/account.json (59.074078ms)
|
||||
✔ D3 numeric version 1 only across all record kinds (1.2284ms)
|
||||
✔ D4 nested unknown keys and missing per-kind required fields refuse (62.71224ms)
|
||||
✔ D5 unenrolled default refuses even when account exists (57.309775ms)
|
||||
✔ D6 provider/account credential type must match (57.474814ms)
|
||||
✔ D7 every harness endpoint enforces HTTP opt-in and shape (0.500041ms)
|
||||
✔ D8 URLs reject embedded credentials and unsupported protocols without echo (158.923658ms)
|
||||
✔ D9 malformed JSON diagnostics contain no content excerpt (60.652615ms)
|
||||
✔ D10 missing metadata is missing-path, not invalid-json (66.683931ms)
|
||||
✔ D10 library returns no partial entries on any invalid record (70.060086ms)
|
||||
✔ null/scalar/array metadata refuses without stack or echo (237.237892ms)
|
||||
✔ credential sibling is never opened, even when an unreadable symlink (28.386518ms)
|
||||
✔ oversized metadata refuses before parsing (52.99023ms)
|
||||
ℹ tests 69
|
||||
ℹ suites 0
|
||||
ℹ pass 69
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 2057.732207
|
||||
@@ -0,0 +1,18 @@
|
||||
baseline: green
|
||||
M01 packages/bus/src/broker.mjs:47 KILLED; expected killer: agents.test.mjs:203 length 50
|
||||
M02 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:204 oldest-first ids
|
||||
M03 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:203 length 50
|
||||
M04 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:189 current holder still holds the unread one
|
||||
M05 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:174 released-run entry
|
||||
M06 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:192 read message must not be listed
|
||||
M07 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:230 per business
|
||||
M08 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:221 per role
|
||||
M09 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:174 entry shape
|
||||
M10 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:174 entry shape
|
||||
M11 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:174 entry shape
|
||||
M12 packages/bus/src/broker.mjs:1053 KILLED (error marker in log; check the reason); expected killer: agents.test.mjs:58 key list
|
||||
M13 packages/bus/src/broker.mjs:932 KILLED; expected killer: agents.test.mjs:62 role session keys
|
||||
M14 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:239 routed, not yet received, must not be listed
|
||||
M15 packages/bus/src/broker.mjs:1052 KILLED; expected killer: agents.test.mjs:174 prototype check
|
||||
manifest after restore: clean
|
||||
processes with cwd in worktree: [('1384647', '/mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e'), ('1385671', '/mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e'), ('1503974', '/mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e')]
|
||||
@@ -0,0 +1,29 @@
|
||||
bus exit=0
|
||||
business exit=0
|
||||
cli exit=0
|
||||
control-board exit=0
|
||||
conversation exit=0
|
||||
discord exit=0
|
||||
harness exit=0
|
||||
ledger exit=0
|
||||
mosaic exit=0
|
||||
queue exit=0
|
||||
runs exit=0
|
||||
seat exit=0
|
||||
tasks exit=0
|
||||
webui exit=0
|
||||
script-test-auth exit=0
|
||||
script-test-conductor exit=0
|
||||
script-test-config exit=0
|
||||
script-test-discord exit=0
|
||||
script-test-extension-package exit=0
|
||||
script-test-foundation exit=0
|
||||
script-test-queue exit=0
|
||||
script-test-release exit=0
|
||||
script-test-task exit=0
|
||||
mutants exit=0
|
||||
leftover pid=1384647 cwd=/mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e
|
||||
leftover pid=1385671 cwd=/mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e
|
||||
leftover pid=1503974 cwd=/mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e
|
||||
processes with cwd in worktree: 3
|
||||
PHASE-B-DONE
|
||||
@@ -0,0 +1,160 @@
|
||||
✔ F1: an ordinary commit after update-ref is refused until step 8; then it commits and the queue stays at C (1082.005864ms)
|
||||
✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (946.954011ms)
|
||||
ℹ 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 (840.048959ms)
|
||||
ℹ 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 (929.540444ms)
|
||||
✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (945.344717ms)
|
||||
✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (891.531455ms)
|
||||
✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1133.925272ms)
|
||||
✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (859.005568ms)
|
||||
✔ F1: a shared-index change during the procedure is not committed (965.497411ms)
|
||||
✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (929.033371ms)
|
||||
✔ F1: a missing or a different hook refuses (831.319051ms)
|
||||
✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1403.530826ms)
|
||||
✔ F1: the canary refuses a hook that git would not run (512.111719ms)
|
||||
✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (782.174021ms)
|
||||
✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (740.871084ms)
|
||||
✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (785.601517ms)
|
||||
✔ bootstrap: the archived tests and validator run outside any repository (736.653004ms)
|
||||
✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (757.03057ms)
|
||||
✔ general: a queue write after the snapshot is not committed (1069.140653ms)
|
||||
✔ general: a snapshot whose log does not extend the base refuses (799.397428ms)
|
||||
✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (151.309063ms)
|
||||
✔ general: environment overrides, a linked worktree and usage (480.48648ms)
|
||||
✔ general: a queue path staged before the run refuses at step 1 (522.036751ms)
|
||||
✔ general: HEAD's queue tests failing in the archive refuse (738.861146ms)
|
||||
✔ genesis document serializes deterministically and replays (3.786375ms)
|
||||
✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (2.170536ms)
|
||||
✔ a tampered result, receipt or viewSha fails replay (2.353726ms)
|
||||
✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.165556ms)
|
||||
✔ add: defaults for an ordinary seat, privileged extras, refusals (4.362897ms)
|
||||
✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (4.422516ms)
|
||||
✔ matrix: release, review round, changes requested and waiting-on-jason (14.059125ms)
|
||||
✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (8.19838ms)
|
||||
✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (18.140939ms)
|
||||
✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (2.362213ms)
|
||||
✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1334.137043ms)
|
||||
✔ matrix: blocked keeps the claim and returns only to previousState (4.613955ms)
|
||||
✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.715088ms)
|
||||
✔ field edits: who may change what (7.320773ms)
|
||||
✔ set issues keeps a logged narrowing of closes (N10) (3.343907ms)
|
||||
✔ text the table shows refuses \ and <, everywhere it enters (N8) (2.295441ms)
|
||||
✔ every accepted text renders to nine cells on every row (N8) (31.03874ms)
|
||||
✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.81319ms)
|
||||
✔ times and dates must be calendar values, not just the shape (2026-10-04) (2.114158ms)
|
||||
✔ replay holds every op id to the caller's rule (N11) (5.176392ms)
|
||||
✔ note: owner, listed reviewer or privileged; empty clears (1.533578ms)
|
||||
✔ assign moves the claim with the owner; done clears it (2.636587ms)
|
||||
✔ render is byte-stable and escapes pipes (0.774196ms)
|
||||
✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (1.002226ms)
|
||||
✔ next: resume, then review, then start, then wait, then nothing; lowest id first (23.620681ms)
|
||||
✔ canonical args make a retry's identity independent of list order (0.312953ms)
|
||||
✔ manifests, headings and blob ids (0.447065ms)
|
||||
✔ the migration map: one queue-map block, exact keys (0.593625ms)
|
||||
✔ every call but `queue` reaches the seat CLI exactly as before A2 (502.239452ms)
|
||||
✔ no argument reaches the app entry in packages/cli, and only that call (71.006317ms)
|
||||
✔ `queue` reaches the queue CLI with the rest of the arguments (188.633994ms)
|
||||
✔ the pre-A2 fixture is the script A2 changed (0.31639ms)
|
||||
✔ acquire publishes the record by link; release removes only its own lock (5.611588ms)
|
||||
✔ a kill between the temp write and the link leaves no lock (46.576473ms)
|
||||
✔ a short or failed temp write refuses and leaves no lock and no temp (2.250661ms)
|
||||
✔ a link error other than EEXIST refuses (1.421696ms)
|
||||
✔ an error after the link releases the lock: unreadable gate, failing temp stat (3.220424ms)
|
||||
✔ a release that fails on a gate path is reported, never a stack trace (P1) (4.215451ms)
|
||||
✔ a paused holder: another writer waits 10 s, then refuses naming it live (10062.957466ms)
|
||||
✔ two concurrent unlockers: the second refuses on the gate (31.184237ms)
|
||||
✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (2.503141ms)
|
||||
✔ a writer publishing during an unlock, gate first: the writer releases and refuses (2.406378ms)
|
||||
✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (3.779082ms)
|
||||
✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (5.319487ms)
|
||||
✔ the same pid and start on a different boot is mismatch (0.529091ms)
|
||||
✔ a foreign host is unknown whatever the local pid says; unlock refuses (74.881591ms)
|
||||
✔ unreadable /proc: classification is unknown and acquire refuses (0.940512ms)
|
||||
✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.232384ms)
|
||||
✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (4.086607ms)
|
||||
✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (2.223432ms)
|
||||
✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (1.023232ms)
|
||||
✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (3.639067ms)
|
||||
✔ the migration map validates and renders the golden genesis table (3.18459ms)
|
||||
✔ the marked QUEUE.md holds every row and parked item between its markers (1.201941ms)
|
||||
✔ map-check reports each kind of drift (3.699626ms)
|
||||
✔ a request posts once as the requester; a retry sends nothing (547.993836ms)
|
||||
✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (2909.772261ms)
|
||||
✔ the pre-send checks: GET user must name the requester, under the deadline (911.028212ms)
|
||||
✔ the lead's request refuses a token for login sage (529.035508ms)
|
||||
✔ the credential file: the seat's own, 0600, no symlink, never the shared default (418.650203ms)
|
||||
✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (1310.387225ms)
|
||||
✔ a same-op retry after a kill sends nothing, even with a stale view (2187.119653ms)
|
||||
✔ a held lock at the outcome exits 3 and names what the transport said (516.833428ms)
|
||||
✔ late outcomes: after an abandon, and after a resolve with the same or another id (1778.74639ms)
|
||||
✔ resolve checks the comment: issue, markers, round, candidate and author (1548.393948ms)
|
||||
✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (445.883145ms)
|
||||
✔ validateRow checks a request round's shape, which every replayed entry must keep (296.555732ms)
|
||||
✔ request, changes, a new candidate, approval: every round pinned; no review files (1228.03134ms)
|
||||
✔ a row with no reviewers opens a round that posts nothing (722.014568ms)
|
||||
✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1024.59534ms)
|
||||
✔ semantics: v1 entries replay as before; review entries need v2 (320.100339ms)
|
||||
✔ set reviewers refuses the row's owner (2026-09-28) (232.507084ms)
|
||||
✔ the owner records no verdict, even as a listed reviewer (295.697854ms)
|
||||
✔ a request comment over the length limit is not sent (336.437761ms)
|
||||
✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (345.127512ms)
|
||||
✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (1578.014139ms)
|
||||
✔ genesis: refusals before anything is written (400.656726ms)
|
||||
✔ genesis: the map must be committed, well formed, with committed briefs and seats (552.348735ms)
|
||||
✔ genesis: markers, a stray witness, once only; a retry returns the receipt (489.95696ms)
|
||||
✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (291.620402ms)
|
||||
✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (554.409104ms)
|
||||
✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (459.830086ms)
|
||||
✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (600.598033ms)
|
||||
✔ a retried add returns the id it first allocated, after reassignment and after done (564.097901ms)
|
||||
✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (398.160819ms)
|
||||
✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (821.910285ms)
|
||||
✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (574.717919ms)
|
||||
✔ add, set reviewers and assign refuse the row's owner as a reviewer (342.030177ms)
|
||||
✔ the working-brief check: a changed working copy refuses the start and flags next (457.983483ms)
|
||||
✔ next: resume first, then nothing for an idle seat; needs a seat (263.214264ms)
|
||||
✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (517.41605ms)
|
||||
✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1048.045358ms)
|
||||
✔ a hand edit to queue.json refuses every verb, reads included (490.126225ms)
|
||||
✔ verify and render --check leave bytes and mtimes unchanged (356.734633ms)
|
||||
✔ render is byte-stable across runs and repositories (215.490223ms)
|
||||
✔ snapshot and verify --snapshot (830.870169ms)
|
||||
✔ usage errors exit 4 (619.594404ms)
|
||||
✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (167.3492ms)
|
||||
✔ a rename failure: nothing visible, temp removed (114.354328ms)
|
||||
✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (151.89523ms)
|
||||
✔ a directory fsync failure, then sync names the op (287.265218ms)
|
||||
✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (126.095355ms)
|
||||
✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (114.195393ms)
|
||||
✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (124.712517ms)
|
||||
✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (107.144318ms)
|
||||
✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (132.264725ms)
|
||||
✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (162.815717ms)
|
||||
✔ unlock prints a swapped gate's warning on stderr, the result on stdout (116.446287ms)
|
||||
✔ a view write that fails keeps the op and reports a stale view (99.663546ms)
|
||||
✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (538.526895ms)
|
||||
✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (583.763651ms)
|
||||
✔ SIGKILL after the witness, before the view: the stale refusal names the op (593.299301ms)
|
||||
✔ SIGKILL after the view, before the receipt: the retry returns the receipt (548.19694ms)
|
||||
✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (116.510193ms)
|
||||
✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (782.596099ms)
|
||||
✔ a deleted witness: refused after the locked recheck; accept-history records it absent (173.060622ms)
|
||||
✔ a header edit during a write: the op stands, the view write is skipped with a warning (117.853234ms)
|
||||
✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (125.313867ms)
|
||||
✔ rows: every row as show prints it, in list order; writes nothing; same notes and refusals as list (146.813265ms)
|
||||
✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (108.1562ms)
|
||||
✔ a writer paused before and after the witness rename: readers see a tail, then a match (118.693365ms)
|
||||
✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (463.91322ms)
|
||||
✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (426.788069ms)
|
||||
✔ the platform check refuses other filesystems (101.4556ms)
|
||||
✔ tmpfs passes only a test layer that allows it (N5) (130.400128ms)
|
||||
✔ unlock keeps a multi-line lock record on stdout (P3) (140.405269ms)
|
||||
ℹ tests 150
|
||||
ℹ suites 0
|
||||
ℹ pass 150
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 19899.783227
|
||||
@@ -0,0 +1,51 @@
|
||||
✔ isRunId accepts the run id shape and nothing else (0.839104ms)
|
||||
✔ a missing data root or runs directory lists nothing (1.241592ms)
|
||||
✔ runs as a regular file lists nothing, as before (0.518131ms)
|
||||
✔ listRunIds keeps r- names only, sorted oldest first (1.101218ms)
|
||||
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (1.185651ms)
|
||||
✔ readRunRecord returns documents and artifacts in directory order (0.830111ms)
|
||||
✔ readRunRecord is null for a missing run, a dangling link or a file (0.54698ms)
|
||||
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.557261ms)
|
||||
✔ readRunDocument reads only the three run documents (0.464188ms)
|
||||
✔ a link inside the data root is followed (0.778886ms)
|
||||
✔ a data root that is itself a link is trusted as configured (0.488625ms)
|
||||
✔ a run directory linked out of the data root is never read (0.513147ms)
|
||||
✔ a document linked out of the data root reads as null (0.766719ms)
|
||||
✔ a runs directory linked out of the data root refuses (0.446042ms)
|
||||
✔ a relative link that climbs out of the data root refuses (0.394819ms)
|
||||
✔ a sibling whose name starts with the data root's name is outside it (0.431096ms)
|
||||
✔ a link loop refuses instead of reading as missing (0.512134ms)
|
||||
✔ an unreadable runs directory refuses instead of listing nothing (0.345708ms)
|
||||
✔ an unreadable run directory refuses instead of reading as missing (0.408821ms)
|
||||
✔ a link to the data root's parent is outside it (0.335262ms)
|
||||
✔ listRunIds sorts whatever order the directory returns (0.616123ms)
|
||||
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.399641ms)
|
||||
✔ the readers write nothing (1.643207ms)
|
||||
✔ no pointer is null (1.451026ms)
|
||||
✔ the pointer release.sh writes reads back (0.948436ms)
|
||||
✔ a pointer that isn't the version 1 shape refuses with exit 2 (12.717557ms)
|
||||
✔ a pointer that is a directory refuses with exit 4 (0.514589ms)
|
||||
✔ a state file linked out of the data root refuses (1.332698ms)
|
||||
✔ a state directory linked out of the data root refuses (0.466981ms)
|
||||
✔ a state directory linked out to nothing reads as no release and an empty log (0.419202ms)
|
||||
✔ a missing log is empty (0.314891ms)
|
||||
✔ the log reads oldest first and counts malformed lines (0.803535ms)
|
||||
✔ last must be a positive integer (0.395359ms)
|
||||
✔ the state readers write nothing (0.682136ms)
|
||||
✔ list prints each run in the established format (61.951998ms)
|
||||
✔ list prints a damaged result.json as unknown and keeps listing the runs after it (59.528091ms)
|
||||
✔ list prints each result field that isn't a string as unknown or - (62.984416ms)
|
||||
✔ show prints the run in the established format (66.236454ms)
|
||||
✔ show of a missing run and an invalid id exit 4 as before (95.565522ms)
|
||||
✔ list and show refuse a runs directory linked out of the data root (126.326882ms)
|
||||
✔ show refuses a run linked out of the data root; list reports it unknown (119.217706ms)
|
||||
✔ list shows an r- name that isn't a valid run id, as before (61.948212ms)
|
||||
✔ show refuses an invalid id before reading the config (31.727663ms)
|
||||
ℹ tests 43
|
||||
ℹ suites 0
|
||||
ℹ pass 43
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 737.803898
|
||||
@@ -0,0 +1,17 @@
|
||||
OK status with missing harness credential exits 3 and still lists accounts
|
||||
OK status reports harness credential (read-only) + mosaic accounts
|
||||
OK api key material never reaches output
|
||||
OK oauth token material never reaches output
|
||||
OK unparseable credential file exits 2
|
||||
OK symlinked credential file exits 4
|
||||
OK env-side credential names reported
|
||||
OK env var values never reach output
|
||||
OK accounts without an accounts dir reports none and creates nothing
|
||||
OK accounts lists files and marks the active one
|
||||
OK loose account perms flagged in listing
|
||||
OK agent --auth with missing account file refuses (exit 4)
|
||||
OK agent --auth with non-0600 account file refuses
|
||||
OK agent --auth with invalid account name refuses
|
||||
OK auth.sh without valid config refuses
|
||||
|
||||
selftest: 15 passed, 0 failed
|
||||
@@ -0,0 +1,23 @@
|
||||
On branch refactor
|
||||
Your branch is up to date with 'origin/refactor'.
|
||||
|
||||
nothing to commit, working tree clean
|
||||
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
|
||||
OK dry-run committed nothing
|
||||
OK apply: allowed change exits 0 (exit 0)
|
||||
OK apply: attribution in commit subject
|
||||
OK apply: target tree clean after commit
|
||||
OK disallowed path refused (exit 1)
|
||||
OK disallowed path: target untouched
|
||||
OK syntax gate refused broken .mjs (exit 1)
|
||||
OK syntax gate: target untouched
|
||||
OK suite failure refused (exit 1)
|
||||
OK suite failure: target reverted to clean
|
||||
OK disabled policy refused (exit 2)
|
||||
OK disabled policy: target untouched
|
||||
OK failed run refused (exit 1)
|
||||
OK failed run: target untouched
|
||||
OK missing run exits 4 (exit 4)
|
||||
OK invalid policy exits 2 (exit 2)
|
||||
|
||||
selftest: 17 passed, 0 failed
|
||||
@@ -0,0 +1,26 @@
|
||||
OK absent adapter defaults to pi
|
||||
OK adapter mock validates (exit 0)
|
||||
OK unsupported adapter exits 2 (exit 2)
|
||||
OK env exports adapter
|
||||
OK bootstrap creates default when absent (exit 0)
|
||||
OK bootstrap wrote config file
|
||||
OK bootstrap is idempotent on existing config (exit 0)
|
||||
OK bootstrap did not rewrite existing config
|
||||
OK validate missing config exits 3 (exit 3)
|
||||
OK malformed JSON exits 2 (exit 2)
|
||||
OK unsupported configVersion exits 2 (exit 2)
|
||||
OK unknown top-level key exits 2 (exit 2)
|
||||
OK unknown execution key exits 2 (exit 2)
|
||||
OK unsupported backend exits 2 (exit 2)
|
||||
OK unsupported environment exits 2 (exit 2)
|
||||
OK relative dataRoot exits 2 (exit 2)
|
||||
OK non-canonical dataRoot exits 2 (exit 2)
|
||||
OK filesystem root dataRoot exits 2 (exit 2)
|
||||
OK home directory dataRoot exits 2 (exit 2)
|
||||
OK dataRoot containing config dir exits 2 (exit 2)
|
||||
OK control character in provider exits 2 (exit 2)
|
||||
OK symlinked config file exits 2 (exit 2)
|
||||
OK env exports resolve correctly
|
||||
OK failed validation modified nothing
|
||||
|
||||
selftest: 24 passed, 0 failed
|
||||
@@ -0,0 +1,70 @@
|
||||
toolchain: node v26.8.1
|
||||
|
||||
OK syntax: packages/discord/src/approvals.mjs
|
||||
OK syntax: packages/discord/src/authorize.mjs
|
||||
OK syntax: packages/discord/src/binding.mjs
|
||||
OK syntax: packages/discord/src/cli.mjs
|
||||
OK syntax: packages/discord/src/connector.mjs
|
||||
OK syntax: packages/discord/src/context.mjs
|
||||
OK syntax: packages/discord/src/engine-pi.mjs
|
||||
OK syntax: packages/discord/src/errors.mjs
|
||||
OK syntax: packages/discord/src/gateway.mjs
|
||||
OK syntax: packages/discord/src/git.mjs
|
||||
OK syntax: packages/discord/src/journal.mjs
|
||||
OK syntax: packages/discord/src/notify.mjs
|
||||
OK syntax: packages/discord/src/rest.mjs
|
||||
OK syntax: packages/discord/src/setspark.mjs
|
||||
OK syntax: packages/discord/src/tools.mjs
|
||||
OK syntax: packages/discord/src/web.mjs
|
||||
OK syntax: packages/discord/bin/git-credential.mjs
|
||||
OK syntax: packages/discord/extension/tools.mjs
|
||||
OK syntax: packages/discord/tests/approvals.test.mjs
|
||||
OK syntax: packages/discord/tests/authorize.test.mjs
|
||||
OK syntax: packages/discord/tests/binding.test.mjs
|
||||
OK syntax: packages/discord/tests/connector.test.mjs
|
||||
OK syntax: packages/discord/tests/context.test.mjs
|
||||
OK syntax: packages/discord/tests/engine.test.mjs
|
||||
OK syntax: packages/discord/tests/fake-pi.mjs
|
||||
OK syntax: packages/discord/tests/gateway.test.mjs
|
||||
OK syntax: packages/discord/tests/git.test.mjs
|
||||
OK syntax: packages/discord/tests/helpers.mjs
|
||||
OK syntax: packages/discord/tests/journal.test.mjs
|
||||
OK syntax: packages/discord/tests/notify.test.mjs
|
||||
OK syntax: packages/discord/tests/recover.test.mjs
|
||||
OK syntax: packages/discord/tests/rest.test.mjs
|
||||
OK syntax: packages/discord/tests/setspark.test.mjs
|
||||
OK syntax: packages/discord/tests/tools.test.mjs
|
||||
OK syntax: packages/discord/tests/web.test.mjs
|
||||
OK syntax: packages/discord/fixtures/claim-worker.mjs
|
||||
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
|
||||
OK syntax: scripts/discord.sh
|
||||
OK syntax: scripts/discord-service.sh
|
||||
OK packages/discord declares no dependencies
|
||||
OK no bot-token-shaped string in packages/discord
|
||||
OK fixture binding uses placeholder ids only
|
||||
OK fixture binding validates
|
||||
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
|
||||
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
|
||||
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
|
||||
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
|
||||
OK real pi with protocol vault adds reserve_id to the git verbs
|
||||
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
|
||||
OK real pi refuses a git key on a read-only root (fail closed)
|
||||
OK real pi with the pilot flags (--no-tools) exposes no tool at all
|
||||
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
|
||||
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
|
||||
OK node --test packages/discord/tests/ (ℹ pass 178)
|
||||
OK scripts/discord.sh --help exits 0
|
||||
OK scripts/discord.sh check without a binding exits 4
|
||||
OK scripts/discord.sh recover without a binding exits 4
|
||||
OK scripts/discord.sh reload without a binding exits 4
|
||||
OK scripts/discord-service.sh without a command exits 4
|
||||
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
|
||||
OK service install writes the rendered unit (0644) and leaves no temp file
|
||||
OK service install a second time reports unchanged
|
||||
OK systemd-analyze verify accepts the rendered unit
|
||||
OK service uninstall removes the unit file
|
||||
OK service install with an unknown flag exits 4
|
||||
OK service install with USER unset finishes and names the account for lingering
|
||||
|
||||
discord suite: 66 passed, 0 failed
|
||||
@@ -0,0 +1,21 @@
|
||||
OK initial ordinary-file install
|
||||
OK installed tree matches canonical source
|
||||
OK installed tree has no symlinks
|
||||
OK check detects installation drift
|
||||
OK sync refuses to overwrite installation drift
|
||||
OK check detects an extra destination file
|
||||
OK check detects an extra destination directory
|
||||
OK check rejects a destination symlink
|
||||
OK sync accepts a canonical source update
|
||||
OK updated installation matches canonical source
|
||||
/mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e/scripts/test-extension-package.sh: line 14: 1467337 Killed "$@" > /dev/null 2>&1
|
||||
OK forced interruption kills the replacing process
|
||||
OK next invocation recovers old consistent installation
|
||||
OK interrupted replacement rolled back
|
||||
OK sync succeeds after interruption recovery
|
||||
OK unlocked stale lock file does not block
|
||||
OK active lock refuses a concurrent sync
|
||||
OK source symlink fails closed
|
||||
OK nested second entrypoint fails closed
|
||||
|
||||
extension package selftest: 18 passed, 0 failed
|
||||
@@ -0,0 +1,53 @@
|
||||
toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0
|
||||
|
||||
OK syntax: scripts/foundation-inspect.mjs
|
||||
OK syntax: scripts/foundation/strict-json.mjs
|
||||
OK syntax: scripts/foundation/canonical.mjs
|
||||
OK syntax: scripts/foundation/resolve.mjs
|
||||
OK syntax: scripts/foundation/validate-record.mjs
|
||||
OK syntax: scripts/foundation/fixtures/build-fixtures.mjs
|
||||
OK syntax: scripts/foundation/canonical.test.mjs
|
||||
OK syntax: scripts/foundation/cli.test.mjs
|
||||
OK syntax: scripts/foundation/fixtures.test.mjs
|
||||
OK syntax: scripts/foundation/resolve.test.mjs
|
||||
OK syntax: scripts/foundation/strict-json.test.mjs
|
||||
OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)
|
||||
OK fixture generator runs
|
||||
OK checked-in fixtures/bundles equal a fresh generation
|
||||
OK checked-in fixtures/raw equal a fresh generation
|
||||
OK checked-in fixtures/index.json equal a fresh generation
|
||||
OK checked-in demo bundles equal a fresh generation
|
||||
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
|
||||
OK node --test scripts/foundation/ (ℹ pass 80)
|
||||
OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)
|
||||
platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999)
|
||||
node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases)
|
||||
schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
|
||||
profile column (schema-valid records only): profile-valid 510, profile-invalid 30
|
||||
profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns
|
||||
OK oracle: zero schema-column disagreements with the pinned checker
|
||||
OK oracle: strict-only profile refusals are counted and asserted
|
||||
OK demo: permitted read preview exits 0 (exit 0)
|
||||
OK demo: permitted file.change preview exits 0 (exit 0)
|
||||
OK demo: assignment.change proposal is unresolved (exit 3) (exit 3)
|
||||
OK demo: revoked registration is refused (exit 3) (exit 3)
|
||||
OK demo: message is not authority (exit 3) (exit 3)
|
||||
OK usage: no arguments exits 2 (exit 2)
|
||||
OK io: missing file exits 4 (exit 4)
|
||||
OK io: directory exits 4 (exit 4)
|
||||
OK io: symlink exits 4 (O_NOFOLLOW) (exit 4)
|
||||
OK bound: oversize fixture exits 2 (exit 2)
|
||||
OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2)
|
||||
OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2)
|
||||
OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0)
|
||||
OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed
|
||||
OK text output starts with the disclaimer
|
||||
OK json output is valid JSON with result allowed and exactly the charter §7 fields
|
||||
OK json golden matches byte-for-byte
|
||||
OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal
|
||||
OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs
|
||||
OK canary never printed (bundle run and credential-file run)
|
||||
OK a non-bundle JSON file is refused at the shape gate, not read into output
|
||||
OK no field of the non-bundle file is echoed
|
||||
|
||||
selftest: 44 passed, 0 failed
|
||||
@@ -0,0 +1,35 @@
|
||||
toolchain: node v26.8.1, git version 2.55.0
|
||||
|
||||
OK syntax: packages/queue/src/cli.mjs
|
||||
OK syntax: packages/queue/src/errors.mjs
|
||||
OK syntax: packages/queue/src/io.mjs
|
||||
OK syntax: packages/queue/src/lock.mjs
|
||||
OK syntax: packages/queue/src/queue.mjs
|
||||
OK syntax: packages/queue/src/review.mjs
|
||||
OK syntax: packages/queue/src/store.mjs
|
||||
OK syntax: packages/queue/tests/commit.test.mjs
|
||||
OK syntax: packages/queue/tests/data.test.mjs
|
||||
OK syntax: packages/queue/tests/dispatch.test.mjs
|
||||
OK syntax: packages/queue/tests/helpers.mjs
|
||||
OK syntax: packages/queue/tests/lock.test.mjs
|
||||
OK syntax: packages/queue/tests/migration.test.mjs
|
||||
OK syntax: packages/queue/tests/review.test.mjs
|
||||
OK syntax: packages/queue/tests/store.test.mjs
|
||||
OK syntax: packages/queue/tests/write.test.mjs
|
||||
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
|
||||
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
|
||||
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
|
||||
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
|
||||
OK syntax: scripts/queue-commit.sh
|
||||
OK syntax: scripts/git-hooks/pre-commit
|
||||
OK syntax: scripts/mosaic
|
||||
OK queue-commit.sh, the guard and scripts/mosaic are executable
|
||||
OK packages/queue declares no dependencies
|
||||
ℹ tests 150
|
||||
ℹ pass 150
|
||||
ℹ fail 0
|
||||
OK node --test packages/queue/tests/
|
||||
OK scripts/mosaic queue help
|
||||
skip queue verify and render --check: this checkout (/mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
|
||||
|
||||
queue suite: 27 passed, 0 failed
|
||||
@@ -0,0 +1,7 @@
|
||||
OK valid RELEASE resolves (exit 0)
|
||||
OK invalid RELEASE exits 1 (exit 1)
|
||||
OK missing RELEASE exits 1 (exit 1)
|
||||
OK valid RELEASE leaves image tag consistent with version
|
||||
skip state-machine cases (docker daemon unavailable)
|
||||
|
||||
selftest: 4 passed, 0 failed
|
||||
@@ -0,0 +1,32 @@
|
||||
OK valid task validates (exit 0)
|
||||
OK unknown task key exits 2 (exit 2)
|
||||
OK unsupported taskVersion exits 2 (exit 2)
|
||||
OK invalid task id exits 2 (exit 2)
|
||||
OK empty prompt exits 2 (exit 2)
|
||||
OK NUL in expectExact exits 2 (exit 2)
|
||||
OK out-of-range timeout exits 2 (exit 2)
|
||||
OK missing mission file exits 4 (exit 4)
|
||||
OK task with valid mission validates (exit 0)
|
||||
OK invalid mission exits 2 (exit 2)
|
||||
OK validate missing task exits 4 (exit 4)
|
||||
OK validation does not modify the task file
|
||||
OK prune dry-run exits 0 (exit 0)
|
||||
OK dry-run deleted nothing
|
||||
OK prune --keep=2 --yes removes oldest (exit 0)
|
||||
OK kept exactly 2 newest runs
|
||||
OK newest run kept, oldest pruned
|
||||
OK append-only receipt written (3 entries)
|
||||
OK sessions/workspaces untouched by prune
|
||||
OK prune with invalid keep exits 4 (exit 4)
|
||||
skip adapter seam cases (docker daemon unavailable)
|
||||
skip workspace/capability cases (docker daemon unavailable)
|
||||
skip live task cases (docker unavailable)
|
||||
OK onboard without name exits 4 (non-interactive) (exit 4)
|
||||
OK onboard --name renders profile (exit 0)
|
||||
OK profile written
|
||||
OK canon structure: required filled, optional placeholdered
|
||||
OK canon sections present
|
||||
skip live recall pair: user recall run, recalled user name (docker daemon unavailable)
|
||||
OK no agent identity on headless run
|
||||
|
||||
selftest: 26 passed, 0 failed
|
||||
@@ -0,0 +1,100 @@
|
||||
✔ a first launch clones the root at its branch, with the root's path as origin (29.991596ms)
|
||||
✔ the clone is not at its place until git clone has finished (21.281722ms)
|
||||
✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (66.183364ms)
|
||||
✔ a clone ahead of the root keeps its commits and counts as current (35.791081ms)
|
||||
▶ a dirty clone, one on another branch and one diverged are kept as they are
|
||||
✔ a modified file (28.569038ms)
|
||||
✔ an untracked file (27.982297ms)
|
||||
✔ a staged file (30.74343ms)
|
||||
✔ another branch (28.325404ms)
|
||||
✔ a detached HEAD (28.199645ms)
|
||||
✔ a diverged branch (42.330972ms)
|
||||
✔ a dirty clone, one on another branch and one diverged are kept as they are (187.290476ms)
|
||||
▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it
|
||||
✔ core.fsmonitor (32.065509ms)
|
||||
✔ core.hooksPath (31.693887ms)
|
||||
✔ a hook file (29.366631ms)
|
||||
✔ include.path (32.400635ms)
|
||||
✔ includeIf (33.344413ms)
|
||||
✔ a filter driver (31.402543ms)
|
||||
✔ origin moved (31.57963ms)
|
||||
✔ a second origin url (31.326178ms)
|
||||
✔ the origin url twice, both the root (32.294069ms)
|
||||
✔ an ext:: origin (31.616713ms)
|
||||
✔ a branch tracking another remote (31.420971ms)
|
||||
✔ a branch merging a tag (30.365029ms)
|
||||
✔ a fetch line onto local branches (31.363851ms)
|
||||
✔ no origin (31.965398ms)
|
||||
✔ a config file over the size cap (28.594444ms)
|
||||
✔ config a symlink to /dev/zero (28.594616ms)
|
||||
✔ the clone a symlink to the bait (28.795506ms)
|
||||
✔ the clone a file (27.995603ms)
|
||||
✔ the clone a plain directory (28.743234ms)
|
||||
✔ .git a gitdir: file (28.690707ms)
|
||||
✔ .git a symlink (27.93877ms)
|
||||
✔ .git/logs a symlink (28.402673ms)
|
||||
✔ .git/refs/heads a symlink (28.462222ms)
|
||||
✔ .git/objects a symlink (27.666117ms)
|
||||
✔ a reflog hard-linked to a file outside (28.053643ms)
|
||||
✔ HEAD a fifo (29.207536ms)
|
||||
✔ a commondir file (28.431409ms)
|
||||
✔ an alternates file (27.872018ms)
|
||||
✔ a gitlink staged, no .gitmodules (40.634997ms)
|
||||
✔ a gitlink and .gitmodules staged (42.386244ms)
|
||||
✔ a gitlink to a repository with its own gitlink (50.255665ms)
|
||||
✔ a gitlink and .gitmodules committed (44.699379ms)
|
||||
✔ a gitlink and submodule.active in config (42.108879ms)
|
||||
✔ a .git/modules with no gitlink (31.846502ms)
|
||||
✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1096.404261ms)
|
||||
▶ a root that can't be cloned refuses with the project id and leaves nothing behind
|
||||
✔ missing (12.049398ms)
|
||||
✔ not a repository (10.010642ms)
|
||||
✔ a plain directory inside another repository (14.14532ms)
|
||||
✔ unreadable (9.558077ms)
|
||||
✔ on no branch (11.513314ms)
|
||||
✔ missing an object (14.818735ms)
|
||||
✔ a root that can't be cloned refuses with the project id and leaves nothing behind (72.938898ms)
|
||||
✔ a clone whose root is on no branch, or gone, is kept and named (26.260309ms)
|
||||
✔ no credential or forge remote of the root reaches a clone (49.37331ms)
|
||||
✔ every project in the business file gets its own clone at its own branch (29.490397ms)
|
||||
✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (1.463316ms)
|
||||
✔ sandboxProblem names a missing bwrap and a bwrap that fails (7.711516ms)
|
||||
✔ every bind says why; the bundle and session file stay read-only over the run directory (1.276222ms)
|
||||
✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (1743.855622ms)
|
||||
✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2612.197892ms)
|
||||
✔ resolveSeat: by name under --repo resolves the repo layout (1.252437ms)
|
||||
✔ resolveSeat: by path resolves the fleet layout (0.33918ms)
|
||||
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.720097ms)
|
||||
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.663613ms)
|
||||
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.791777ms)
|
||||
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.282723ms)
|
||||
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.139809ms)
|
||||
✔ CLI launch: registers, execs the fake launch script, and passes args through (31.057712ms)
|
||||
✔ CLI launch: --harness lands in the record (29.833873ms)
|
||||
✔ CLI launch: the launch script's own exit code passes through (29.19263ms)
|
||||
✔ CLI launch: relaunching a seat rewrites the one registration record (53.813845ms)
|
||||
✔ CLI launch: omitting --task records an empty string, not null (26.851197ms)
|
||||
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (80.107164ms)
|
||||
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (132.358233ms)
|
||||
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.382824ms)
|
||||
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.584409ms)
|
||||
✔ 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.687276ms)
|
||||
✔ updateTask: records setBy, preserves startedAt and every unrelated field, upgrades an old record in place only when the task is set, and replaces a previous attribution (0.83904ms)
|
||||
✔ 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 (261.619163ms)
|
||||
✔ family: exactly one launch.max key in the model name, else null (0.664202ms)
|
||||
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.650727ms)
|
||||
✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.244184ms)
|
||||
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.418694ms)
|
||||
✔ session file and launch log: 0600, the session file written once (1.033956ms)
|
||||
✔ endReason maps the runner's exit codes; a signal is killed (0.114073ms)
|
||||
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.229592ms)
|
||||
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (0.962134ms)
|
||||
✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (189.872092ms)
|
||||
ℹ tests 89
|
||||
ℹ suites 0
|
||||
ℹ pass 89
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 4433.558289
|
||||
@@ -0,0 +1,18 @@
|
||||
baseline: green
|
||||
M01 packages/bus/src/broker.mjs:47 KILLED; expected killer: agents.test.mjs:203 length 50
|
||||
M02 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:204 oldest-first ids
|
||||
M03 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:203 length 50
|
||||
M04 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:189 current holder still holds the unread one
|
||||
M05 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:174 released-run entry
|
||||
M06 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:192 read message must not be listed
|
||||
M07 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:230 per business
|
||||
M08 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:221 per role
|
||||
M09 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:174 entry shape
|
||||
M10 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:174 entry shape
|
||||
M11 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:174 entry shape
|
||||
M12 packages/bus/src/broker.mjs:1053 KILLED (error marker in log; check the reason); expected killer: agents.test.mjs:58 key list
|
||||
M13 packages/bus/src/broker.mjs:932 KILLED; expected killer: agents.test.mjs:62 role session keys
|
||||
M14 packages/bus/src/broker.mjs:1048 KILLED; expected killer: agents.test.mjs:239 routed, not yet received, must not be listed
|
||||
M15 packages/bus/src/broker.mjs:1052 KILLED; expected killer: agents.test.mjs:174 prototype check
|
||||
manifest after restore: clean
|
||||
processes with cwd in worktree: [('1384647', '/mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e'), ('1385671', '/mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e'), ('1503974', '/mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e')]
|
||||
@@ -0,0 +1,97 @@
|
||||
✔ the boot config is checked before anything starts (14.572894ms)
|
||||
✔ a business with no tracker entry refuses task verbs (15.354ms)
|
||||
✔ credential.expiring and .expired are recorded once per instance (32.5578ms)
|
||||
✔ a token file that changes on disk records credential.changed (14.078322ms)
|
||||
✔ autostart polls, reconciles and retries a startup the tracker was down for (38.474154ms)
|
||||
✔ a refusal a restart must clear is not retried by the poll (11.293151ms)
|
||||
✔ a poll that fires while two are queued is dropped (13.068761ms)
|
||||
✔ close waits for a running verb and refuses one that has not started (43.316639ms)
|
||||
✔ the bundled Vikunja is the pinned upstream image the runbook names (1.302404ms)
|
||||
✔ every published port is on 127.0.0.1, and no secret is in the file (0.456036ms)
|
||||
✔ the committed table loads frozen; its verbs are the broker task verbs (2.103489ms)
|
||||
✔ the schema refuses any other shape (3.524089ms)
|
||||
✔ each verb refuses, asks for a decision and writes as the table and the role files say (170.607336ms)
|
||||
✔ the list holds the PRD's requirement ids, in its order, at its version (5.144148ms)
|
||||
✔ the committed list loads frozen (0.559628ms)
|
||||
✔ the schema refuses any other shape (2.493796ms)
|
||||
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (105.084495ms)
|
||||
✔ the project list holds recorded project objects in the recorded list envelope (11.412705ms)
|
||||
✔ the recorded task bodies pass the checks S3 applies to every read (0.510773ms)
|
||||
✔ the client works against the fake over real HTTP with the platform fetch (98.610298ms)
|
||||
✔ a correct install starts, and the first reconcile records tasks that already exist (33.277203ms)
|
||||
✔ verbs refuse while a business is starting and after startup refused it (18.74649ms)
|
||||
✔ startup refuses a token that can do more than its role needs (46.914181ms)
|
||||
✔ startup refuses an unsupported version and flags an untested one (34.683868ms)
|
||||
✔ startup refuses a board that the runbook did not install (36.944223ms)
|
||||
✔ startup refuses a project the sync bot cannot read (8.488176ms)
|
||||
✔ startup refuses a configured label the pm bot cannot see (8.515539ms)
|
||||
✔ startup refuses an expired credential and a missing sync credential (14.276966ms)
|
||||
✔ an unreachable tracker refuses with tracker-unavailable (6.434281ms)
|
||||
✔ an edit in the UI is recorded once, with the fields that changed (79.645774ms)
|
||||
✔ a move between open buckets is seen on the board, though updated does not change (42.539428ms)
|
||||
✔ a person's comment is counted and a bot's is not (73.674975ms)
|
||||
✔ the hourly reconcile catches a comment through comment_count (77.574247ms)
|
||||
✔ a task closed in the UI leaves the open view with its done bucket (119.135178ms)
|
||||
✔ a task that leaves the board is recorded as deleted, moved or out of reach (65.541624ms)
|
||||
✔ a poll that read before a verb wrote does not overwrite the verb (36.624832ms)
|
||||
✔ a tracker fault during a tick is reported and the next tick catches up (36.1194ms)
|
||||
✔ a malformed answer refuses the tick with tracker-shape (12.578186ms)
|
||||
✔ no token value reaches the database, the log or a refusal (44.07937ms)
|
||||
✔ the first look at a task counts only comments inside the window (35.256958ms)
|
||||
✔ with descendants off the adapter reads no project list and no child, as in v0 (51.366293ms)
|
||||
✔ a three-level tree is read per project, and a project outside it is not (25.682388ms)
|
||||
✔ a watch-only child keeps the business ready, and a failed child read refuses as transient (27.355598ms)
|
||||
✔ a root without the installed buckets refuses with descendants on (9.70814ms)
|
||||
✔ the project list refuses without read_all, and skips archived projects and saved filters (24.284431ms)
|
||||
✔ descendants must be true or false (9.172735ms)
|
||||
✔ a move inside the tree is a moved tombstone plus a snapshot under the new ref (61.938643ms)
|
||||
✔ a child made after startup joins the tree at the next reconcile (56.417408ms)
|
||||
✔ a child that leaves the tree has its tasks recorded missing, and the ledger keeps their history (62.086824ms)
|
||||
✔ a child deleted or hidden between reconciles drops out at the next tick, and the root still refuses (82.645832ms)
|
||||
✔ a reconcile that fails keeps the previous tree (33.461187ms)
|
||||
✔ a closed task the cursor missed takes its own project's done bucket (34.919658ms)
|
||||
✔ closed tasks read at startup take their own project's done bucket (9.219546ms)
|
||||
✔ a closed task the cursor returns takes its own project's done bucket (33.978146ms)
|
||||
✔ a task moved out of a child that leaves the tree at the same time is moved, not no-access (33.210093ms)
|
||||
✔ a task in a bucket outside the five keeps its raw bucket id (30.745623ms)
|
||||
✔ bucket 0 on a watch-only task never becomes a bucket or a state (77.920573ms)
|
||||
✔ a child whose kanban view is deleted still refuses the tick, and the reconcile reads it watch-only (55.32458ms)
|
||||
✔ a child dropped at a tick comes back at the next reconcile once the bot reads it again (35.429073ms)
|
||||
✔ task.create needs a recorded human request and a requirement id (42.549734ms)
|
||||
✔ task.create takes only an approved requirement id (27.294691ms)
|
||||
✔ task.create cites the request a received message carries, not the message's id (22.274802ms)
|
||||
✔ only labels named in the business file can be written (29.699165ms)
|
||||
✔ task.schedule sets and clears a due date and relations (35.905726ms)
|
||||
✔ assign and reassign move the role bots and record task.assigned (53.481418ms)
|
||||
✔ task.update.assigned is for the assignee and records task.state (48.890652ms)
|
||||
✔ a wrong expected digest records task.conflict and writes nothing (23.781021ms)
|
||||
✔ a cross-role verb needs a resolved decision, used once (22.611906ms)
|
||||
✔ task.close needs a verdict; after it every verb refuses with task-done (20.991044ms)
|
||||
✔ a lost answer is settled by a re-read and never retried (26.586106ms)
|
||||
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (35.687778ms)
|
||||
✔ a task the sync bot cannot read refuses and records nothing (18.019939ms)
|
||||
✔ verbs and polls for one business run one at a time (86.635551ms)
|
||||
✔ a due date with milliseconds is written to the second (61.73321ms)
|
||||
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (36.433114ms)
|
||||
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (12.765961ms)
|
||||
✔ a create whose final read fails succeeds and records task.created (33.148438ms)
|
||||
✔ an edit between the last write and the final read shows as external on the next poll (34.635341ms)
|
||||
✔ task.created is recorded when a later label write fails (11.261517ms)
|
||||
✔ every verb acts in an installed child, on the child's own board (42.327664ms)
|
||||
✔ task.create refuses a project outside the tree, a watch-only one and one that is not an id (23.892838ms)
|
||||
✔ with descendants off, task.create takes only the root project (11.693504ms)
|
||||
✔ a verb on a watch-only task, or a relation to one, refuses with project-not-installed (15.991052ms)
|
||||
✔ a related task is read before authority: in another project than its ref names, task-moved; missing, task-not-found (22.771157ms)
|
||||
✔ a task moved to another project of the tree refuses its old ref with task-moved and takes its new one (14.28572ms)
|
||||
✔ a verb and a create in the child record the final read of the child's board (13.199454ms)
|
||||
✔ a create in the child whose final read fails records the child's todo bucket (33.524461ms)
|
||||
✔ a lost answer in the child is settled by a re-read of the child's board (19.377038ms)
|
||||
✔ every related task is read before authority, not only the first (14.709692ms)
|
||||
ℹ tests 89
|
||||
ℹ suites 0
|
||||
ℹ pass 89
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1011.624473
|
||||
@@ -0,0 +1,116 @@
|
||||
✔ 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 (4940.594053ms)
|
||||
✔ 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 (6077.477035ms)
|
||||
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3181.065839ms)
|
||||
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
|
||||
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4087.55761ms)
|
||||
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7934.089506ms)
|
||||
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (128.535586ms)
|
||||
✔ decisions route serves the roles and recent lists from the reader view (120.080992ms)
|
||||
✔ credentials route serves the broker credential status from the reader view (41.580351ms)
|
||||
✔ replies route serves the replies to one message from the reader view (226.421905ms)
|
||||
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (162.02554ms)
|
||||
✔ bus refusals map to statuses and never carry a path or a stack (58.242878ms)
|
||||
✔ humanCall speaks the human transport protocol without blocking the server (2729.970027ms)
|
||||
✔ busReader takes --business, else the live bus host, else refuses (833.765322ms)
|
||||
✔ humanCall kills a transport that runs past its timeout (548.116945ms)
|
||||
✔ serve refuses a --business value that is not a business id (847.538591ms)
|
||||
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (2930.897378ms)
|
||||
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2955.780341ms)
|
||||
✔ conversation view: a newer session with no readable history keeps the marker (1662.432747ms)
|
||||
✔ conversation view: seats without history say so and offer no reply (976.652052ms)
|
||||
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3578.851538ms)
|
||||
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54256.26942ms)
|
||||
✔ messages HTTP read pages and preserves metadata; bad arguments refuse before a read; other reads unchanged (209.770455ms)
|
||||
✔ 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 (4365.804553ms)
|
||||
✔ Messages nav count polls on Tasks and Board and clears on a Board refusal; messages 403 refuses local pages (3046.361814ms)
|
||||
✔ copy writes the Console paths at the full id, read-only, and leaves an existing copy alone (2450.620163ms)
|
||||
✔ copy refuses a commit off refactor, an unknown one, a worktree and a bad copies directory (1796.47216ms)
|
||||
✔ copy writes nothing when an archived path is missing at the commit (570.527417ms)
|
||||
✔ a copy that loses the rename race leaves no read-only temp directory behind (785.476326ms)
|
||||
✔ a copy whose rename fails with no target refuses, writes no copy and leaves no temp directory (628.883501ms)
|
||||
✔ a copy that meets a target holding files says exists: and moves nothing into it (389.72752ms)
|
||||
✔ a copy that meets an empty target directory replaces it by rename, not by moving into it (314.941417ms)
|
||||
✔ serve runs from a copy: /healthz 200, the config check, roles and RELEASE from the copy, the queue from --checkout (985.141329ms)
|
||||
✔ serve refuses a --checkout that is not an absolute directory path (409.24293ms)
|
||||
✔ consoleReads runs the queue read under queueRoot and reads RELEASE under root (73.924062ms)
|
||||
✔ preview options are all three or none, and each is checked (12.438335ms)
|
||||
✔ the CLI refuses a partial or invalid preview flag set at start (3020.766263ms)
|
||||
✔ the CLI with all three flags serves the preview mode (419.513339ms)
|
||||
✔ a request for the public name needs the header equal to the user id, else 401 and nothing read (57.236605ms)
|
||||
✔ the header name matches without regard to case, and the value compare is exact (11.525067ms)
|
||||
✔ the public name with the header reads the bus views, and the Host and Origin are checked first (72.367067ms)
|
||||
✔ in preview mode every write answers 403 read-only, from the public name and loopback, and reaches nothing (39.003333ms)
|
||||
✔ loopback requests are unchanged in preview mode, and the bus views work with the board down (10.58263ms)
|
||||
✔ a proxy that rewrites Host to the loopback port gets 403 on its first request, whatever the user (65.512008ms)
|
||||
✔ a repeated Host is 400 before any other check, in preview mode and without it (15.982971ms)
|
||||
✔ without the preview flags the public name is refused and writes still reach the board (77.35396ms)
|
||||
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.104446ms)
|
||||
✔ credential state from the date alone, as the bus states it (0.249245ms)
|
||||
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2311.937399ms)
|
||||
✔ queue: a refused read fails closed with the store's text, redacted (906.448172ms)
|
||||
✔ queue: a store that fails to load, or a child that dies, gives a fixed message with no path or stack (2302.073626ms)
|
||||
✔ business: names, vars on the allowlist, authority, credential metadata only (54.422498ms)
|
||||
✔ business: missing or invalid file refuses with the module's text, redacted (73.411355ms)
|
||||
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (63.785767ms)
|
||||
✔ settings: release, board, loopback address, notifier binding name only (312.490967ms)
|
||||
✔ task fields: the committed table with no reads configured, GET only; a bad table refuses with no path (39.844062ms)
|
||||
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4004.430406ms)
|
||||
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23377.940748ms)
|
||||
✔ 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 (6835.776214ms)
|
||||
✔ a sent receipt lists its replies as the page refreshes, oldest first and capped, and marks none delivered or read (11396.599508ms)
|
||||
✔ startServer takes send only as a function and only with the preview mode (12.176898ms)
|
||||
✔ the CLI refuses --send without the preview flags, and without a configured bus (1340.593643ms)
|
||||
✔ the CLI with --send and a configured bus sends through the human transport and logs one line (361.643887ms)
|
||||
✔ without --send the route is read-only like every other write, and config says no send (32.057293ms)
|
||||
✔ with --send every other write is still 403 read-only, and nothing reaches the board or bus (20.892383ms)
|
||||
✔ config says send only on the public name with --send on (7.102684ms)
|
||||
✔ a send without the signed-in user is 401 and is neither sent nor logged, loopback included (10.258754ms)
|
||||
✔ each request check refuses before anything is sent, and logs the refusal without the body (44.549562ms)
|
||||
✔ a send passes exactly to, body and in_reply_to, answers the id, and logs it (8.811603ms)
|
||||
✔ a crafted role name cannot forge a log line (13.011055ms)
|
||||
✔ a bus refusal is not sent, with its status; an outcome the bus could not confirm is unknown and never retried (24.653895ms)
|
||||
✔ through the human transport the request is exactly the business, message.send and the keys (341.259949ms)
|
||||
✔ against a real broker: the message is written once as a human send, and its refusals map (143.443528ms)
|
||||
✔ loopback host and board origin fail closed (14.28307ms)
|
||||
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (363.065787ms)
|
||||
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (139.191571ms)
|
||||
✔ unreachable board reports URL; CLI rejects unsupported options (1026.311024ms)
|
||||
✔ render prints the unit without the preview flags (25.774539ms)
|
||||
✔ render carries every serve flag, the preview set included (58.85662ms)
|
||||
✔ a partial preview set is refused (177.960171ms)
|
||||
✔ a value the unit cannot carry is refused, for every flag (1564.221275ms)
|
||||
✔ the rendered unit names none of the human proof agent markers and sets no agent variable (80.879218ms)
|
||||
✔ install into --dir with --no-reload writes the file whole, then reports unchanged (208.99448ms)
|
||||
✔ install without --no-reload runs daemon-reload and nothing that starts the unit (83.006421ms)
|
||||
✔ uninstall refuses while the unit is running, with or without --no-reload, and removes it once it is not (392.023217ms)
|
||||
✔ status prints the unit state and GET /healthz on the installed port (334.37413ms)
|
||||
✔ usage: an unknown verb or flag, and start or enable, exit 4 (113.292872ms)
|
||||
✔ without --send, render is the template with its placeholders filled, byte for byte (76.829372ms)
|
||||
✔ render --send with the preview set ends ExecStart with --send and changes nothing else (186.727422ms)
|
||||
✔ --send without the whole preview set is refused and writes nothing (352.850773ms)
|
||||
✔ install --send into --dir with --no-reload writes the sending unit, and status says whether it sends (452.158683ms)
|
||||
✔ uninstall and status take no --send (54.508558ms)
|
||||
✔ install refuses while the unit is running, in any state but inactive or failed, and changes nothing (327.061991ms)
|
||||
✔ when the state read fails, install and uninstall refuse with one line and change nothing (65.914467ms)
|
||||
✔ install needs --copy naming a copy that exists, and refuses outside the canonical checkout without --allow-worktree (62.903994ms)
|
||||
✔ install refuses unless the bus unit runs from the same copy, naming both paths, and changes nothing (184.855647ms)
|
||||
✔ render --copy runs the copy and reads the queue in the checkout; without --copy the unit is unchanged (14.465064ms)
|
||||
✔ the uid header takes only an Authentik uid; the username header still takes a username (251.616382ms)
|
||||
✔ a preview value cannot start with - or . (29.75083ms)
|
||||
✔ status names the copy and whether refactor has moved past it, and says when the unit runs from no copy (407.77175ms)
|
||||
✔ tokens.css and icons.svg are byte copies of docs/design (2.83506ms)
|
||||
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (3.398768ms)
|
||||
✔ shipped strings are unchanged, and the web keeps its write boundary (3.734829ms)
|
||||
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7254.243801ms)
|
||||
✔ five states on Board, Inbox, Tasks, Agents and Trail (2436.240518ms)
|
||||
✔ Queue, Business and Settings in their five states, with no move button (4702.713189ms)
|
||||
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1094.105562ms)
|
||||
✔ the server log names no config path when the system config refuses (213.762783ms)
|
||||
ℹ tests 107
|
||||
ℹ suites 0
|
||||
ℹ pass 107
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 54808.106687
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 80 Phase B runner, round 2, candidate base 57a2ddc5978b05b780dc0e69fb4e6c2a60ab5877.
|
||||
# Usage: bash <packet>/phase-b.sh
|
||||
# Any nonzero step exit stops the run: no later suite, script or mutant runs. The stop is
|
||||
# recorded in out/phase-b-summary.txt and the run ends PHASE-B-STOPPED. A full pass ends
|
||||
# PHASE-B-DONE. Output goes to out/ through tee.
|
||||
set -u
|
||||
cd / || exit 1
|
||||
WT=/mnt/storage/src/mosaic-stack/.claude/worktrees/agent-abe72e00ca2bcdf7e
|
||||
PK=$WT/agents/researcher/work/queue-80/r1
|
||||
OUT=$PK/out
|
||||
export DOCKER_HOST=unix:///nonexistent.sock
|
||||
mkdir -p "$OUT"
|
||||
SUM=$OUT/phase-b-summary.txt
|
||||
: > "$SUM"
|
||||
|
||||
# finish RC: scan for processes with a cwd in the worktree, write the end marker, exit RC.
|
||||
finish() {
|
||||
local left=0 p pid c
|
||||
for p in /proc/[0-9]*; do
|
||||
pid=${p#/proc/}
|
||||
c=$(readlink "$p/cwd" 2>/dev/null) || continue
|
||||
case "$c" in
|
||||
"$WT"|"$WT"/*) echo "leftover pid=$pid cwd=$c" >> "$SUM"; left=$((left + 1)) ;;
|
||||
esac
|
||||
done
|
||||
echo "processes with cwd in worktree: $left" >> "$SUM"
|
||||
if [ "$1" -eq 0 ]; then echo "PHASE-B-DONE" >> "$SUM"; else echo "PHASE-B-STOPPED" >> "$SUM"; fi
|
||||
exit "$1"
|
||||
}
|
||||
|
||||
# step NAME DIR COMMAND... runs COMMAND in DIR, tees to out/NAME.log, records exit code and TAP totals.
|
||||
# A nonzero exit records a STOP line and calls finish 1.
|
||||
step() {
|
||||
local name=$1 dir=$2
|
||||
shift 2
|
||||
( cd "$dir" && "$@" ) 2>&1 | tee "$OUT/$name.log" > /dev/null
|
||||
local rc=${PIPESTATUS[0]}
|
||||
echo "$name exit=$rc" >> "$SUM"
|
||||
grep -E '^# (tests|suites|pass|fail|cancelled|skipped|todo) ' "$OUT/$name.log" | sed 's/^/ /' >> "$SUM"
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
echo "STOP: $name exited $rc; later steps and mutants not run" >> "$SUM"
|
||||
finish 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Suites, in the order of the packages directory. Each uses its package.json test form.
|
||||
step bus "$WT/packages/bus" node --test 'tests/*.test.mjs'
|
||||
step business "$WT/packages/business" node --test tests/
|
||||
step cli "$WT/packages/cli" node --test 'tests/*.test.mjs'
|
||||
step control-board "$WT/packages/control-board" node --test tests/
|
||||
step conversation "$WT/packages/conversation" node --test tests/
|
||||
step discord "$WT/packages/discord" node --test tests/
|
||||
step harness "$WT/packages/harness" node --test 'tests/*.test.mjs'
|
||||
step ledger "$WT/packages/ledger" node --test 'tests/*.test.mjs'
|
||||
step mosaic "$WT/packages/mosaic" node --test tests/
|
||||
step queue "$WT/packages/queue" node --test tests/
|
||||
step runs "$WT/packages/runs" node --test tests/
|
||||
step seat "$WT/packages/seat" node --test tests/
|
||||
step tasks "$WT/packages/tasks" node --test 'tests/*.test.mjs'
|
||||
step webui "$WT/packages/webui" node --test tests/
|
||||
|
||||
# The nine scripts/test-*.sh, sequentially, from the worktree root.
|
||||
for s in "$WT"/scripts/test-*.sh; do
|
||||
step "script-$(basename "$s" .sh)" "$WT" bash "$s"
|
||||
done
|
||||
|
||||
# The 15 mutants last.
|
||||
python3 -I "$PK/mutants.py" "$WT" "$PK" 2>&1 | tee "$OUT/mutants-run.log" > /dev/null
|
||||
rc=${PIPESTATUS[0]}
|
||||
echo "mutants exit=$rc" >> "$SUM"
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
echo "STOP: mutants exited $rc" >> "$SUM"
|
||||
finish 1
|
||||
fi
|
||||
|
||||
finish 0
|
||||
@@ -0,0 +1,176 @@
|
||||
diff --git a/packages/bus/README.md b/packages/bus/README.md
|
||||
index 527768b1a..ded8b9289 100644
|
||||
--- a/packages/bus/README.md
|
||||
+++ b/packages/bus/README.md
|
||||
@@ -108,7 +108,9 @@ committed. Callers must inspect its evidence before deciding what to do; they
|
||||
must not resend automatically. `message.receive` records delivery when the
|
||||
broker hands the batch to the connection, not when the harness acts on it.
|
||||
A lost response leaves an uncertain delivery, never automatic replay to a new
|
||||
-holder. `message.read` is an explicit acknowledgement. `trail(messageId)` lets
|
||||
+holder. A holder that releases a role without reading what it received leaves
|
||||
+those messages stranded: no later holder receives or reads them, and `agents`
|
||||
+lists them under `stranded` (Agent state, below). `message.read` is an explicit acknowledgement. `trail(messageId)` lets
|
||||
an operator inspect content and delivery evidence after an uncertain result.
|
||||
|
||||
| Verb | Arguments and result |
|
||||
@@ -127,7 +129,7 @@ an operator inspect content and delivery evidence after an uncertain result.
|
||||
| `launch.revoke`, `launch.restore` | No args; outside-agent human CLI only. |
|
||||
| `inbox` | No args; plain JSON projection scoped to capability's business. |
|
||||
| `tasks` | Optional args, scoped to capability's business (row 89, #1583). None: every row of the tasks projection (below). `{task_ref}` alone: that one row, or `task-not-found`. Any of `{done,assignee,label,requirement,limit,after}`: `{tasks,total,next}`, the short form (below). An unknown arg or bad value refuses `invalid-request`. |
|
||||
-| `agents` | No args; each held role's latest claim row in the capability's business. Human and reader sessions also get `state`, `working` and `inputNeeded` (Agent state, below); a role session gets the claim rows only. |
|
||||
+| `agents` | No args; each held role's latest claim row in the capability's business. Human and reader sessions also get `state`, `working`, `inputNeeded` and `stranded` (Agent state, below); a role session gets the claim rows only. |
|
||||
| `trail` | `{subject}`: task ref, decision UUID or message UUID. Linked records in broker write order. |
|
||||
| `decisions` | `{list}`: `roles` or `recent`; human and reader sessions only. Open decisions routed to a role, or the last 50 closed. |
|
||||
| `credentials` | No args; human and reader sessions only. `[{service,account,role,dateKind,date,state}]` for the capability's business, never a token, file path or variable name. |
|
||||
@@ -149,6 +151,12 @@ records the bus already keeps. Nothing new is written and no schema changed.
|
||||
- `state` is `working` when `working` is set, else `waiting` when
|
||||
`inputNeeded` is nonempty, else `idle`. Working wins over waiting, as on
|
||||
the control board.
|
||||
+- `stranded` lists messages to the role that an earlier holder was delivered
|
||||
+ and never read, as `{id, from, run, at}`: the sender (`human` for the CLI),
|
||||
+ the run it was delivered to, and when. Oldest first, at most 50, `[]` when
|
||||
+ none. The bus neither redelivers, routes nor marks these read, and the
|
||||
+ released run can no longer read them, so they stay listed. A person who
|
||||
+ still needs one sends it again as a new message.
|
||||
|
||||
Why the bus and not a seat-to-role map: the business file's `holder` is a
|
||||
display name, and the claim's holder is a run, not a seat, so no committed
|
||||
@@ -159,7 +167,8 @@ Limits: a message routed but not yet received shows idle. A reply that fails
|
||||
for a reason other than a gone session leaves its message unread, so the role
|
||||
stays working; `since` shows how long. No error or offline state is derived.
|
||||
A new holder doesn't inherit the previous holder's unread deliveries;
|
||||
-decisions stay with the role. Unheld and released roles aren't listed.
|
||||
+decisions stay with the role. Unheld and released roles aren't listed, so a
|
||||
+message stranded for a role nobody holds shows once a run claims the role.
|
||||
|
||||
Unknown keys, roles, references, actions and verbs refuse. If writing refusal
|
||||
evidence fails, the caller receives the static `storage-unavailable` code. `human` is the reserved
|
||||
diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs
|
||||
index 09a18633e..eadc3f953 100644
|
||||
--- a/packages/bus/src/broker.mjs
|
||||
+++ b/packages/bus/src/broker.mjs
|
||||
@@ -43,6 +43,8 @@ const CLOSED = "('resolved','withdrawn','expired')";
|
||||
export const RECENT = 50;
|
||||
// How many replies `replies` returns, oldest first.
|
||||
export const REPLIES = 50;
|
||||
+// Row 80 (#1574): how many stranded messages `agents` lists for each role, oldest first.
|
||||
+export const STRANDED = 50;
|
||||
// Row 89 (#1583): the arguments `tasks` takes, and its page size by default and at most.
|
||||
const TASK_ARGS = ['done', 'assignee', 'label', 'requirement', 'limit', 'after', 'task_ref'];
|
||||
export const TASK_PAGE = 50;
|
||||
@@ -1039,7 +1041,16 @@ export class Broker {
|
||||
.map((d) => ({ id: d.id, question: d.question, class: d.class, blocking: !!d.blocking, task_ref: d.task_ref, at: d.at }));
|
||||
const working = busy.messages ? { messages: busy.messages, since: busy.since } : null;
|
||||
const state = working ? 'working' : inputNeeded.length ? 'waiting' : 'idle';
|
||||
- return { ...c, state, working, inputNeeded };
|
||||
+ // Row 80 (#1574): messages to the role that an earlier holder was delivered and never read.
|
||||
+ // The run they went to no longer holds the role. A delivered row already exists, so routing
|
||||
+ // and receive skip them. Listing them writes nothing.
|
||||
+ const stranded = this.#store.all(
|
||||
+ `SELECT m.id, m.from_role AS "from", d.holder_run AS run, d.at FROM messages m JOIN deliveries d ON d.message=m.id AND d.op='delivered' WHERE m.business=? AND m.to_role=? AND d.holder_run IS NOT ? AND NOT EXISTS(SELECT 1 FROM deliveries r WHERE r.message=m.id AND r.op='read') ORDER BY m.seq LIMIT ${STRANDED}`,
|
||||
+ c.business,
|
||||
+ c.role,
|
||||
+ c.holder_run,
|
||||
+ );
|
||||
+ return { ...c, state, working, inputNeeded, stranded };
|
||||
}
|
||||
#claimEnd(s, h, op, decision) {
|
||||
this.#store.run(
|
||||
diff --git a/packages/bus/tests/agents.test.mjs b/packages/bus/tests/agents.test.mjs
|
||||
index 23b5126b3..d92933bd6 100644
|
||||
--- a/packages/bus/tests/agents.test.mjs
|
||||
+++ b/packages/bus/tests/agents.test.mjs
|
||||
@@ -55,7 +55,8 @@ test('agents: a held role with nothing on the bus is idle; a role session gets t
|
||||
['coder', 'idle', null, []],
|
||||
['pm', 'idle', null, []],
|
||||
]);
|
||||
- for (const r of rows) assert.deepEqual(Object.keys(r), [...CLAIM, 'state', 'working', 'inputNeeded']);
|
||||
+ for (const r of rows) assert.deepEqual(Object.keys(r), [...CLAIM, 'state', 'working', 'inputNeeded', 'stranded']);
|
||||
+ assert.deepEqual(rows.map((r) => r.stranded), [[], []]);
|
||||
assert.deepEqual(call(b, reader, 'agents'), rows);
|
||||
const plain = call(b, pm, 'agents');
|
||||
for (const r of plain) assert.deepEqual(Object.keys(r), CLAIM);
|
||||
@@ -157,3 +158,83 @@ test('agents: state reads only its own business, even for a run name both use',
|
||||
const there = byRole(call(b, otherHuman, 'agents')).cto;
|
||||
assert.deepEqual([there.state, there.working.messages, there.inputNeeded.length], ['working', 1, 1]);
|
||||
});
|
||||
+
|
||||
+// Row 80 (#1574): a message delivered to a run that released the role unread is stranded for the
|
||||
+// role. No later holder receives or reads it, and listing it writes no delivery row.
|
||||
+test('agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it', (t) => {
|
||||
+ const { b, store, agent, human } = setup(t);
|
||||
+ const first = agent('pm', 'demo', 'pm-one');
|
||||
+ const sent = call(b, human, 'message.send', { to: 'pm', body: 'left behind' });
|
||||
+ assert.deepEqual(call(b, first, 'message.receive').map((m) => m.id), [sent.id]);
|
||||
+ const before = store.all('SELECT * FROM deliveries');
|
||||
+ call(b, first, 'role.release');
|
||||
+ assert.deepEqual(call(b, human, 'agents'), [], 'a released role is not listed');
|
||||
+ const second = agent('pm', 'demo', 'pm-two');
|
||||
+ const row = byRole(call(b, human, 'agents')).pm;
|
||||
+ assert.deepEqual(row.stranded, [{ id: sent.id, from: 'human', run: 'pm-one', at: delivered(store, sent.id) }]);
|
||||
+ assert.deepEqual(call(b, second, 'message.receive'), []);
|
||||
+ assert.throws(() => call(b, second, 'message.read', { id: sent.id }), { code: 'not-recipient' });
|
||||
+ assert.throws(() => call(b, first, 'message.read', { id: sent.id }), { code: 'not-holder' });
|
||||
+ assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded, row.stranded);
|
||||
+ assert.deepEqual(store.all('SELECT * FROM deliveries'), before, 'the claim and the listing write no delivery row');
|
||||
+});
|
||||
+
|
||||
+test('agents: a message the holder read is not stranded; one the current holder has not read is not listed', (t) => {
|
||||
+ const { b, agent, human } = setup(t);
|
||||
+ const first = agent('pm', 'demo', 'pm-one');
|
||||
+ const read = call(b, human, 'message.send', { to: 'pm', body: 'read first' });
|
||||
+ const unread = call(b, human, 'message.send', { to: 'pm', body: 'still held' });
|
||||
+ call(b, first, 'message.receive');
|
||||
+ call(b, first, 'message.read', { id: read.id });
|
||||
+ assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded, [], 'the current holder still holds the unread one');
|
||||
+ call(b, first, 'role.release');
|
||||
+ agent('pm', 'demo', 'pm-two');
|
||||
+ assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded.map((m) => m.id), [unread.id]);
|
||||
+});
|
||||
+
|
||||
+test('agents: stranded is oldest first and capped at 50 for a role', (t) => {
|
||||
+ const { b, agent, human } = setup(t);
|
||||
+ const first = agent('pm', 'demo', 'pm-one');
|
||||
+ const ids = Array.from({ length: 60 }, (_, i) => call(b, human, 'message.send', { to: 'pm', body: `m${i}` }).id);
|
||||
+ assert.equal(call(b, first, 'message.receive').length, 60);
|
||||
+ call(b, first, 'role.release');
|
||||
+ agent('pm', 'demo', 'pm-two');
|
||||
+ const stranded = byRole(call(b, human, 'agents')).pm.stranded;
|
||||
+ assert.equal(stranded.length, 50);
|
||||
+ assert.deepEqual(stranded.map((m) => m.id), ids.slice(0, 50));
|
||||
+ assert.ok(stranded.every((m) => m.run === 'pm-one' && m.from === 'human'));
|
||||
+});
|
||||
+
|
||||
+test('agents: stranded is per role and per business', (t) => {
|
||||
+ const { b, agent, human } = setup(t);
|
||||
+ const cto = agent('cto', 'demo', 'cto-one');
|
||||
+ const pm = agent('pm', 'demo', 'pm-one');
|
||||
+ const toCto = call(b, human, 'message.send', { to: 'cto', body: 'for cto' });
|
||||
+ const toPm = call(b, human, 'message.send', { to: 'pm', body: 'for pm' });
|
||||
+ call(b, cto, 'message.receive');
|
||||
+ call(b, pm, 'message.receive');
|
||||
+ call(b, cto, 'role.release');
|
||||
+ call(b, pm, 'role.release');
|
||||
+ agent('cto', 'demo', 'cto-two');
|
||||
+ agent('pm', 'demo', 'pm-two');
|
||||
+ const rows = byRole(call(b, human, 'agents'));
|
||||
+ assert.deepEqual(rows.cto.stranded.map((m) => m.id), [toCto.id]);
|
||||
+ assert.deepEqual(rows.pm.stranded.map((m) => m.id), [toPm.id]);
|
||||
+ // The same role and run name in another business stays there.
|
||||
+ const otherHuman = b.bindHuman({ business: 'other', human: 'jason', via: 'cli', outsideAgent: true });
|
||||
+ const otherPm = agent('pm', 'other', 'pm-one');
|
||||
+ const elsewhere = call(b, otherHuman, 'message.send', { to: 'pm', body: 'elsewhere' });
|
||||
+ call(b, otherPm, 'message.receive');
|
||||
+ call(b, otherPm, 'role.release');
|
||||
+ agent('pm', 'other', 'pm-two');
|
||||
+ assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded.map((m) => m.id), [toPm.id]);
|
||||
+ assert.deepEqual(byRole(call(b, otherHuman, 'agents')).pm.stranded.map((m) => m.id), [elsewhere.id]);
|
||||
+});
|
||||
+
|
||||
+// Row 80 (#1574), Sage ruling: a message routed to a held role and not yet received is not stranded.
|
||||
+test('agents: a message routed to a held role and not yet received is not stranded', (t) => {
|
||||
+ const { b, agent, human } = setup(t);
|
||||
+ agent('pm', 'demo', 'pm-one');
|
||||
+ call(b, human, 'message.send', { to: 'pm', body: 'routed, not received' });
|
||||
+ assert.deepEqual(byRole(call(b, human, 'agents')).pm.stranded, []);
|
||||
+});
|
||||
Reference in New Issue
Block a user