review(rocko): row 73 round 1 packet, coder role drops task.reassign (#1567)

Rocko's packet agents/rocko/work/queue-73/ (candidate manifest bf4e5ed6,
packet manifest 41ffcd5c, 53 files), their BUILD-LOG entry and SESSIONS
line. Sage's SESSIONS line for the review request (comment 27467) and
row 79 (#1573) for the stale text the packet names.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-11 18:20:34 -05:00
co-authored by Claude Opus 5.5
parent 4e714509d1
commit 2137dd7f5d
56 changed files with 2864 additions and 0 deletions
+140
View File
@@ -0,0 +1,140 @@
# Row 73 (#1567): drop the coder's task.reassign grant, candidate packet, round 1
Author: Rocko. Reviewer: Dewey. Brief:
`docs/plans/2026-10-11_claude-pin-and-coder-grant.md`, section "Coder role:
drop the task.reassign grant the broker always refuses". Base: `324c064b`
(`base.txt`). At 23:16Z the checkout HEAD `f6b9ea4e` has no change since the
base to any candidate path or to `packages/{bus,business,tasks}/src` or
`packages/harness/src/tools.mjs`. `build.patch` applies to it cleanly. The
packet is uncommitted. I made no commits, pushes or Gitea calls and read no
token or private binding.
## Files (`files.txt`, `candidate-manifest.sha256`)
| File | Change |
|---|---|
| `roles/coder.json` | `crossRole`: `["task.reassign", "task.scope.change"]` → `["task.scope.change"]`. Nothing else in the file changes. |
| `packages/business/tests/role.test.mjs` | "shipped authority follows the note's table": asserts that the coder's `crossRole` is exactly `["task.scope.change"]` |
| `packages/business/tests/resolve.test.mjs` | "classify": the coder's `task.reassign` is now `gated` and its `task.scope.change` is `cross`. "limits.authority narrows cross-role actions too" moves from the coder to the pm (see below). |
| `packages/bus/tests/broker.test.mjs` | +1 test built on the shipped role files: "the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role" |
| `packages/harness/tests/tools.test.mjs` | +1 test: "a coder with a tracker gets its own task tools and no task_reassign" |
| `packages/tasks/tests/world.mjs` | the fixture's coder `crossRole` now matches the shipped role |
| `packages/tasks/tests/verbs.test.mjs` | in "assign and reassign …": a coder's `task.reassign` refuses `field-writer` and records no `action.allowed` |
| `agents/rocko/work/queue-73/mutants.sh` | the mutant rerun: `mutants.sh <tree>` |
No change to `broker.mjs`, `PM_VERBS`, the slice 1 field table or any other
role's grants.
### Why the narrowing test moved to the pm
Before this change, the test limited the coder to `task.scope.change` and
watched `task.reassign` drop out of `crossRole`. With one cross-role action
left, that limit drops nothing from `crossRole`. I checked this: with the new
role file and the old test, the mutant `resolve-cross-unnarrowed` (crossRole
ignores `limits.authority`) passes the test (`out/old-narrow-test-under-mutant.txt`).
The pm still holds two cross-role actions. The test now limits the pm to
`task.create`, `task.close` and `task.priority.change`. It checks that
`crossRole` keeps `task.priority.change` and drops `task.scope.change`, and
that `withinRole` narrows too.
## Every place that read the old grant, and what it says now
Probe (`out/probe-base.txt`, `out/probe-candidate.txt`): the shipped roles
through `validateBusinessDocument`, `resolveInstance`, `busBusiness` and a
real `Broker`, as `bootConfig` assembles them.
| Reader | Before (base) | After (candidate) |
|---|---|---|
| `business/src/resolve.mjs` `resolveInstance` | coder `crossRole` `[task.reassign, task.scope.change]` | `[task.scope.change]`. The resolved digest of every coder instance changes too. Only the bundle manifest records it, and nothing compares it. |
| `business/src/resolve.mjs` `classify` | `cross` | `gated` |
| `bus/src/broker.mjs` `#classification` (via `cli/src/config.mjs` `bootConfig` → `busBusiness`) | `cross-role`; `decision.raise` routes to the domain arbiter, `pm` by default (`cto` for technical) | `gated`; routes to `human` |
| `harness/src/tools.mjs` `typedTools`, with a tracker | the coder gets a `task_reassign` tool | no `task_reassign` tool; `task_scope_change` and `task_update_assigned` remain |
| `harness/src/tools.mjs` `untypedActions` → `bundle.mjs` prompt, without a tracker | the prompt lists `task.reassign` among "Actions you hold that have no bus tool yet" | not listed |
| `tasks/src/verbs.mjs` `PM_VERBS` | `field-writer` before authority | unchanged: `field-writer` before authority |
| `tasks/tests/world.mjs` fixture | copied the old grant | matches the shipped role |
| business, bus, harness and tasks tests | see Files | see Files |
Readers I did not change (outside the brief's files; Sage's call):
- `packages/business/README.md`, lines 30-43: the version 2 example is the
shipped coder file and still shows `"crossRole": ["task.reassign",
"task.scope.change"]`. It goes stale with this change. Suggested fix: drop
`"task.reassign", ` from the example.
- `packages/tasks/README.md`, lines 193-196: "A coder role's `crossRole`
grant of `task.reassign` can't be used. The writer check refuses it before
authority is looked at. …" This will be stale too. Suggested text: "The
coder role holds no `task.reassign` grant (row 73). The writer check
refuses a non-pm `task.reassign` before authority is looked at anyway. If
a coder should hand work on, that's a change to the field table, not to
the role file."
- `packages/tasks/live/run.mjs`, line 32: the live run's own copy of the
slice 1 authority still gives the coder `task.reassign`. Nothing runs it in
the suites. It never has the coder call `task.reassign`, so its result
doesn't change. It should match `world.mjs`.
- Unaffected: `scripts/test-task.sh` line 279 and
`business/tests/role.test.mjs` line 115 are role-validation fixtures, not
the coder's grant. Historical notes are left as they are: the slice 1
plan, the Darkwing and Filbert work notes, and lead decision 90.
## What a coder's task.reassign does end to end after the change
1. A pi coder session has no `task_reassign` tool, so it can't make the
call through the harness.
2. It can still `raise_decision` with `action: task.reassign`, because the
tool's action enum is every action. The broker classes it `gated` and
routes it to the human, where before it went to the pm as cross-role. The
human is then asked a question that can only end in `field-writer`.
3. If the human approves and the coder calls the verb directly on the bus,
the broker's `authorize` passes as `gated`; the bus test shows that. The
task verbs refuse first with `field-writer`, before `authorize`, so nothing
is consumed and nothing is written (tasks test).
The brief names the follow-up but doesn't make it, and nor does this row. To
refuse a non-pm `task.reassign` (or any `PM_VERBS` action) at
`decision.raise`, before a decision opens, is a broker change and would be
its own row.
A bus that is already running keeps the authority it loaded at boot. The
change reaches it on its next restart.
## Mutants (`out/mutants-candidate.txt`, `out/mutants-base.txt`)
| Mutant | Edit | Base `324c064b` | Candidate |
|---|---|---|---|
| `role-keeps-reassign` | `roles/coder.json` puts `task.reassign` back | n/a (the base is this mutant) | killed by role, classify, bus and harness tests |
| `broker-unlisted-cross` | `#classification`: an action in neither list → `cross-role` | **survived** | killed by the new bus test |
| `resolve-cross-unnarrowed` | `resolveInstance`: `crossRole` ignores `limits.authority` | killed | killed (by the narrowing test and the harness test "an action outside the instance's authority has no tool") |
| `classify-unlisted-cross` | `classify`: an unlisted action → `cross` | killed | killed |
| `tools-all-task-verbs` | `typedTools`: a task tool for every verb, held or not | killed | killed (now also by the new harness test) |
The script saves each file, applies the sed and checks it changed something.
It then runs the five test files and restores the file from the saved copy,
so it works on an uncommitted tree. `out/mutants-*/` holds each diff and log.
I ran the candidate twice. The run recorded here uses the final script; the
first run used the same script before a comment-only reflow, with the same
results.
## Gate (`out/gate/`, sequential, scratch worktree at 324c064b with the candidate, `node_modules` linked, 23:09:50Z-23:14:34Z)
| Suite | Result |
|---|---|
| tasks `node --test 'packages/tasks/tests/*.test.mjs'` | 71 pass, 0 fail |
| bus | 81 pass, 0 fail |
| business | 60 pass, 0 fail |
| harness | 58 pass, 0 fail, 0 skipped |
| cli | 112 pass, 0 fail |
| webui | 50 pass, 0 fail |
| seat | 27 pass, 0 fail |
| test-auth | 15 passed, 0 failed |
| test-conductor | 17 passed, 0 failed |
| test-config | 24 passed, 0 failed |
| test-discord | 66 passed, 0 failed |
| test-extension-package | 18 passed, 0 failed |
| test-foundation | 44 passed, 0 failed |
| test-queue | 27 passed, 0 failed |
| test-release | 14 passed, 0 failed |
| test-task | 98 passed, 0 failed (Docker) |
cli, webui and seat go beyond the gate the brief names. I ran them because
they read the shipped roles. After the gate I removed the `rocko-r73_default`
network.
+1
View File
@@ -0,0 +1 @@
324c064b2a9290ff6c17449c3fa0725aa7a09494
+210
View File
@@ -0,0 +1,210 @@
diff --git a/agents/rocko/work/queue-73/mutants.sh b/agents/rocko/work/queue-73/mutants.sh
new file mode 100755
index 00000000..8d550d1f
--- /dev/null
+++ b/agents/rocko/work/queue-73/mutants.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+# Row 73 (#1567): rerun the role-file revert and the mutants against a tree
+# with the candidate. Usage: mutants.sh <tree>. Each mutant edits one line,
+# runs the row's tests, and restores the file from a saved copy, so the tree
+# need not be committed. Prints "killed" or "SURVIVED" per mutant; exit 1 if
+# any survives, a sed doesn't change its file, or the unmutated run fails.
+# The tasks test is in the list but kills nothing here: the verb refuses a
+# coder's task.reassign with or without the grant.
+set -euo pipefail
+TREE="$(cd "${1:?usage: mutants.sh <tree>}" && pwd)"
+cd "$TREE"
+ROLE=roles/coder.json
+BROKER=packages/bus/src/broker.mjs
+RESOLVE=packages/business/src/resolve.mjs
+TOOLS=packages/harness/src/tools.mjs
+TESTS=(
+ packages/bus/tests/broker.test.mjs
+ packages/business/tests/role.test.mjs
+ packages/business/tests/resolve.test.mjs
+ packages/harness/tests/tools.test.mjs
+ packages/tasks/tests/verbs.test.mjs
+)
+run_tests() { node --test "${TESTS[@]}" >"$1" 2>&1; }
+
+# name | file | sed expression
+MUTANTS=(
+ 'role-keeps-reassign|'"$ROLE"'|s/"crossRole": \["task.scope.change"\]/"crossRole": ["task.reassign", "task.scope.change"]/'
+ 'broker-unlisted-cross|'"$BROKER"'|/^ ? .cross-role.$/{n;s/^ : .gated.;$/ : '"'"'cross-role'"'"';/}'
+ 'resolve-cross-unnarrowed|'"$RESOLVE"'|s/^ crossRole = crossRole.filter((a) => vars\["limits.authority"\].includes(a));$/ crossRole = [...crossRole];/'
+ 'classify-unlisted-cross|'"$RESOLVE"'|/^ if (resolved.limits.authority.crossRole.includes(action)) return "cross";$/{n;s/return "gated";/return "cross";/}'
+ 'tools-all-task-verbs|'"$TOOLS"'|s/^ if (held.has(action)) add(name, action, action, description, obj(properties, required));$/ add(name, action, action, description, obj(properties, required));/'
+)
+
+OUT="${OUT:-$TREE/.mutants}"
+mkdir -p "$OUT"
+run_tests "$OUT/baseline.log" || { echo "baseline run failed: $OUT/baseline.log" >&2; exit 1; }
+echo "baseline: pass"
+status=0
+for m in "${MUTANTS[@]}"; do
+ IFS='|' read -r name file expr <<<"$m"
+ cp -p "$file" "$OUT/orig"
+ sed -i -e "$expr" "$file"
+ if cmp -s "$OUT/orig" "$file"; then
+ echo "$name: NOT APPLIED (sed matched nothing)"; status=1; continue
+ fi
+ diff -u "$OUT/orig" "$file" >"$OUT/$name.diff" || true
+ if run_tests "$OUT/$name.log"; then
+ echo "$name: SURVIVED"; status=1
+ else
+ echo "$name: killed ($(grep -E '^✖' "$OUT/$name.log" | grep -v 'failing tests' | sed 's/ ([0-9.]*ms)$//' | sort -u | tr '\n' ';'))"
+ fi
+ cp -p "$OUT/orig" "$file"
+ cmp -s "$OUT/orig" "$file" || { echo "restore failed: $file" >&2; exit 1; }
+ rm -f "$OUT/orig"
+done
+exit "$status"
diff --git a/packages/bus/tests/broker.test.mjs b/packages/bus/tests/broker.test.mjs
index 3e908d70..d5d53d9e 100644
--- a/packages/bus/tests/broker.test.mjs
+++ b/packages/bus/tests/broker.test.mjs
@@ -4,6 +4,9 @@ import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { Store } from '../src/store.mjs';
+import { busBusiness } from '../src/business.mjs';
+import { resolveInstance, validateBusinessDocument } from '../../business/src/index.mjs';
+import { businessDoc, REPO_ROLES, systemFor } from '../../business/tests/helpers.mjs';
const { Broker } = await import('../src/broker.mjs').catch((e) => {
if (e.code === 'ERR_MODULE_NOT_FOUND') return {};
throw e;
@@ -354,3 +357,26 @@ test('both arbiters require human resolution when their cross-role route is them
assert.equal(b.authorize(cap, action, { decision: d.id, target: 'vikunja:3/41' }).class, 'cross-role');
}
});
+
+test('the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role', (t) => {
+ const root = mkdtempSync(join(tmpdir(), 'bus-roles-'));
+ t.after(() => rmSync(root, { recursive: true, force: true }));
+ const business = validateBusinessDocument(businessDoc(root, 'demo'), join(root, 'businesses', 'demo.json'), {
+ rolesDir: REPO_ROLES,
+ });
+ const resolved = {};
+ for (const instance of Object.keys(business.roles))
+ resolved[instance] = resolveInstance({ system: systemFor(root), business, instance });
+ const { b, agent, human } = setup(t, { demo: busBusiness(business, resolved) });
+ const c = agent('coder');
+ call(b, c, 'role.claim');
+ const reassign = raise(b, c, 'task.reassign', { target: 'vikunja:3/7' });
+ assert.equal(reassign.class, 'gated');
+ assert.equal(reassign.route_to, 'human');
+ const scope = raise(b, c, 'task.scope.change', { domain: 'technical', target: 'vikunja:3/7' });
+ assert.equal(scope.class, 'cross-role');
+ assert.equal(scope.route_to, 'cto');
+ // Approval passes the broker; the task verb still refuses a coder with field-writer (packages/tasks).
+ call(b, human, 'decision.resolve', { id: reassign.id, choice: 'yes' });
+ assert.equal(b.authorize(c, 'task.reassign', { decision: reassign.id, target: 'vikunja:3/7' }).class, 'gated');
+});
diff --git a/packages/business/tests/resolve.test.mjs b/packages/business/tests/resolve.test.mjs
index 0995eb5c..75ac2925 100644
--- a/packages/business/tests/resolve.test.mjs
+++ b/packages/business/tests/resolve.test.mjs
@@ -122,21 +122,23 @@ test("limits.authority without role.launch leaves the launcher with no launch bl
});
test("limits.authority narrows cross-role actions too", () => {
+ // pm, the role with two cross-role actions: the limit keeps one and drops the other.
const s = setup((d) => {
- d.roles.coder.vars["limits.authority"] = ["task.update.assigned", "git.push.working", "task.scope.change"];
+ d.roles.pm.vars["limits.authority"] = ["task.create", "task.close", "task.priority.change"];
});
- const coder = resolve(s, "coder");
- assert.deepEqual(coder.limits.authority, { withinRole: ["task.update.assigned", "git.push.working"], crossRole: ["task.scope.change"] });
- assert.equal(classify(coder, "task.reassign"), "gated");
- assert.equal(classify(coder, "task.scope.change"), "cross");
- assert.equal(classify(coder, "review.request"), "gated");
+ const pm = resolve(s, "pm");
+ assert.deepEqual(pm.limits.authority, { withinRole: ["task.create", "task.close"], crossRole: ["task.priority.change"] });
+ assert.equal(classify(pm, "task.priority.change"), "cross");
+ assert.equal(classify(pm, "task.scope.change"), "gated");
+ assert.equal(classify(pm, "task.assign"), "gated");
});
test("classify", () => {
const s = setup();
const coder = resolve(s, "coder");
assert.equal(classify(coder, "git.push.working"), "within");
- assert.equal(classify(coder, "task.reassign"), "cross");
+ assert.equal(classify(coder, "task.scope.change"), "cross");
+ assert.equal(classify(coder, "task.reassign"), "gated");
assert.equal(classify(coder, "task.close"), "gated");
assert.equal(classify(coder, "deploy"), "gated");
assert.equal(classify(resolve(s, "pm"), "task.priority.change"), "cross");
diff --git a/packages/business/tests/role.test.mjs b/packages/business/tests/role.test.mjs
index 1a401307..00628f80 100644
--- a/packages/business/tests/role.test.mjs
+++ b/packages/business/tests/role.test.mjs
@@ -61,6 +61,8 @@ test("shipped role scopes match addendum B section 2 and the SR runbook", () =>
test("shipped authority follows the note's table", () => {
const auth = (name) => loadRole(REPO_ROLES, name).authority;
assert.ok(auth("coder").withinRole.includes("git.push.working"));
+ // Assignee is pm-only (decision 90): a coder grant of task.reassign could never succeed.
+ assert.deepEqual(auth("coder").crossRole, ["task.scope.change"]);
assert.ok(!auth("reviewer").withinRole.includes("git.push.working"));
assert.deepEqual(auth("reviewer").crossRole, []);
assert.ok(auth("pm").withinRole.includes("role.launch"));
diff --git a/packages/harness/tests/tools.test.mjs b/packages/harness/tests/tools.test.mjs
index b520dd4c..31606131 100644
--- a/packages/harness/tests/tools.test.mjs
+++ b/packages/harness/tests/tools.test.mjs
@@ -35,6 +35,14 @@ test("a coder gets no launch, no resolve_decision, and no task tools without a t
assert.ok(!untyped.includes("message.send"));
});
+test("a coder with a tracker gets its own task tools and no task_reassign", (t) => {
+ const coder = resolvedFor(t, "coder");
+ assert.deepEqual(names(typedTools({ resolved: coder, tracker: true })), [
+ "send_message", "task_scope_change", "task_update_assigned",
+ "raise_decision", "list_agents", "inbox", "trail", "list_tasks",
+ ]);
+});
+
test("launch only when the business's launch block names the instance as launcher", (t) => {
const pm = resolvedFor(t, "pm", (d) => delete d.launch);
assert.equal(pm.launch, null);
diff --git a/packages/tasks/tests/verbs.test.mjs b/packages/tasks/tests/verbs.test.mjs
index f5621f7b..c9ea98a6 100644
--- a/packages/tasks/tests/verbs.test.mjs
+++ b/packages/tasks/tests/verbs.test.mjs
@@ -93,6 +93,9 @@ test('assign and reassign move the role bots and record task.assigned', async (t
// The field-writer check runs before authority, so the refused call consumes nothing.
await assert.rejects(w.call(coder, 'task.assign', { task_ref, role: 'coder' }), refusedWith('field-writer'));
assert.equal(allowed(w, 'task.assign').length, 0);
+ // Assignee stays pm-only (decision 90); the coder role holds no task.reassign grant, and the verb refuses it anyway.
+ await assert.rejects(w.call(coder, 'task.reassign', { task_ref, role: 'reviewer' }), refusedWith('field-writer'));
+ assert.equal(allowed(w, 'task.reassign').length, 0);
await assert.rejects(w.call(pm, 'task.assign', { task_ref, role: 'cto' }), refusedWith('unknown-role'));
await assert.rejects(w.call(pm, 'task.assign', { task_ref, role: 'toString' }), refusedWith('unknown-role'));
await assert.rejects(w.call(pm, 'task.reassign', { task_ref, role: 'coder' }), refusedWith('task-unassigned'));
diff --git a/packages/tasks/tests/world.mjs b/packages/tasks/tests/world.mjs
index d50e96ea..5085e779 100644
--- a/packages/tasks/tests/world.mjs
+++ b/packages/tasks/tests/world.mjs
@@ -42,7 +42,7 @@ export const AUTHORITY = {
},
coder: {
withinRole: ['task.update.assigned', 'git.push.working', 'review.request', 'message.send'],
- crossRole: ['task.reassign', 'task.scope.change'],
+ crossRole: ['task.scope.change'],
},
reviewer: { withinRole: ['review.verdict', 'message.send', 'task.update.assigned'], crossRole: [] },
cto: {
diff --git a/roles/coder.json b/roles/coder.json
index 2a7325a1..04220271 100644
--- a/roles/coder.json
+++ b/roles/coder.json
@@ -7,7 +7,7 @@
"network": "api-only",
"authority": {
"withinRole": ["task.update.assigned", "git.push.working", "review.request", "message.send"],
- "crossRole": ["task.reassign", "task.scope.change"]
+ "crossRole": ["task.scope.change"]
},
"credentials": [
{
@@ -0,0 +1,8 @@
e3300acd1f85f7c7c495dc2228ec904c0af992f9ea09693adcf3e5b1121d5f83 agents/rocko/work/queue-73/mutants.sh
6c451be1461b903e2072af0a592b1be33b2e4cf9245e2fa8609ab678d7f39848 packages/business/tests/resolve.test.mjs
17fbdd672e9daca32401b96d8cad0893aaa2ef14db9f3a6f4834e9f8b8f1b50c packages/business/tests/role.test.mjs
bca2e886b021831ada150dfa76be247dbe489072fb249d1a5b63e98582077853 packages/bus/tests/broker.test.mjs
9cd45207694693734207ded38b65c99e59787db74e562668b88a499c4454ed0f packages/harness/tests/tools.test.mjs
5df33892007143de7537ff97059762372c78041ad3bd8be456758d3c51ec984b packages/tasks/tests/verbs.test.mjs
2d1c06d38617d1affd58034f5a5441fe1413b7bc2a2333fedda99c6e26baf2f0 packages/tasks/tests/world.mjs
5b00c49dc1c998156798e0fd9fbe1e1c7e2bb62373dc9f80efaa60902b66b228 roles/coder.json
+8
View File
@@ -0,0 +1,8 @@
agents/rocko/work/queue-73/mutants.sh
packages/business/tests/resolve.test.mjs
packages/business/tests/role.test.mjs
packages/bus/tests/broker.test.mjs
packages/harness/tests/tools.test.mjs
packages/tasks/tests/verbs.test.mjs
packages/tasks/tests/world.mjs
roles/coder.json
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Row 73 (#1567): rerun the role-file revert and the mutants against a tree
# with the candidate. Usage: mutants.sh <tree>. Each mutant edits one line,
# runs the row's tests, and restores the file from a saved copy, so the tree
# need not be committed. Prints "killed" or "SURVIVED" per mutant; exit 1 if
# any survives, a sed doesn't change its file, or the unmutated run fails.
# The tasks test is in the list but kills nothing here: the verb refuses a
# coder's task.reassign with or without the grant.
set -euo pipefail
TREE="$(cd "${1:?usage: mutants.sh <tree>}" && pwd)"
cd "$TREE"
ROLE=roles/coder.json
BROKER=packages/bus/src/broker.mjs
RESOLVE=packages/business/src/resolve.mjs
TOOLS=packages/harness/src/tools.mjs
TESTS=(
packages/bus/tests/broker.test.mjs
packages/business/tests/role.test.mjs
packages/business/tests/resolve.test.mjs
packages/harness/tests/tools.test.mjs
packages/tasks/tests/verbs.test.mjs
)
run_tests() { node --test "${TESTS[@]}" >"$1" 2>&1; }
# name | file | sed expression
MUTANTS=(
'role-keeps-reassign|'"$ROLE"'|s/"crossRole": \["task.scope.change"\]/"crossRole": ["task.reassign", "task.scope.change"]/'
'broker-unlisted-cross|'"$BROKER"'|/^ ? .cross-role.$/{n;s/^ : .gated.;$/ : '"'"'cross-role'"'"';/}'
'resolve-cross-unnarrowed|'"$RESOLVE"'|s/^ crossRole = crossRole.filter((a) => vars\["limits.authority"\].includes(a));$/ crossRole = [...crossRole];/'
'classify-unlisted-cross|'"$RESOLVE"'|/^ if (resolved.limits.authority.crossRole.includes(action)) return "cross";$/{n;s/return "gated";/return "cross";/}'
'tools-all-task-verbs|'"$TOOLS"'|s/^ if (held.has(action)) add(name, action, action, description, obj(properties, required));$/ add(name, action, action, description, obj(properties, required));/'
)
OUT="${OUT:-$TREE/.mutants}"
mkdir -p "$OUT"
run_tests "$OUT/baseline.log" || { echo "baseline run failed: $OUT/baseline.log" >&2; exit 1; }
echo "baseline: pass"
status=0
for m in "${MUTANTS[@]}"; do
IFS='|' read -r name file expr <<<"$m"
cp -p "$file" "$OUT/orig"
sed -i -e "$expr" "$file"
if cmp -s "$OUT/orig" "$file"; then
echo "$name: NOT APPLIED (sed matched nothing)"; status=1; continue
fi
diff -u "$OUT/orig" "$file" >"$OUT/$name.diff" || true
if run_tests "$OUT/$name.log"; then
echo "$name: SURVIVED"; status=1
else
echo "$name: killed ($(grep -E '^✖' "$OUT/$name.log" | grep -v 'failing tests' | sed 's/ ([0-9.]*ms)$//' | sort -u | tr '\n' ';'))"
fi
cp -p "$OUT/orig" "$file"
cmp -s "$OUT/orig" "$file" || { echo "restore failed: $file" >&2; exit 1; }
rm -f "$OUT/orig"
done
exit "$status"
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -u
W=/mnt/storage/scratch/rocko-r73; G=/mnt/storage/scratch/rocko-r73-out/gate
mkdir -p $G; cd $W
date -u +%Y-%m-%dT%H:%M:%SZ > $G/start.txt
: > $G/summary.txt
for p in tasks bus business harness cli webui seat; do
node --test "packages/$p/tests/*.test.mjs" > $G/$p.log 2>&1; rc=$?
echo "$p rc=$rc $(grep -E '^ℹ (pass|fail|skipped)' $G/$p.log | tr '\n' ' ')" >> $G/summary.txt
done
for s in scripts/test-*.sh; do
n=$(basename $s .sh)
bash $s > $G/$n.log 2>&1; rc=$?
echo "$n rc=$rc $(grep -iE '[0-9]+ passed|passed.*failed|failed' $G/$n.log | tail -1)" >> $G/summary.txt
done
date -u +%Y-%m-%dT%H:%M:%SZ > $G/end.txt
@@ -0,0 +1,89 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (75.005416ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (46.010344ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (117.296262ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (88.211853ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (45.454ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (40.349948ms)
✔ task action subjects and linked decision trail are complete and ordered (62.71095ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (19.640675ms)
✔ business isolation includes inherited object names and cross-business message references (30.687724ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (35.606534ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (14.931591ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (30.298419ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (23.683738ms)
✔ both arbiters require human resolution when their cross-role route is themselves (37.164098ms)
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (36.167453ms)
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (22.223275ms)
✔ only validated broker references load; returned data and exceptions cannot expose a known token (6.415097ms)
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (7.757975ms)
✔ expiry refuses use and env references never become client data (3.599696ms)
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (3.510469ms)
✔ opaque tokens shorter than 16 characters refuse before use (0.519448ms)
✔ roles lists open decisions routed to a role, and never the human inbox (62.978066ms)
✔ recent lists closed decisions of every route, newest close first, with how each closed (53.783969ms)
✔ recent stops at RECENT rows (353.1222ms)
✔ every row is what trail already gives the same session for that id (61.037502ms)
✔ a role session can't list decisions, so it can't read another role's open decisions (50.943117ms)
✔ decisions is a read: one business, strict arguments, nothing written (68.290864ms)
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (48.870662ms)
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (29.248538ms)
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (32.637699ms)
✔ endLaunch leaves a claim another run took alone (151.775432ms)
✔ refuse records action.refused against the caller with the code only (25.854569ms)
✔ launches off refuses role.launch with launch-revoked until launches on (41.369233ms)
✔ broker process: launch ops authorize role.launch, record refusals and end runs (183.620622ms)
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (5.460808ms)
✔ process reader gets own kernel identity without exposing environment values (1.105513ms)
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (2.890964ms)
✔ real pid 1 remains inspectable when its environment is protected (0.66196ms)
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (58.516738ms)
✔ missing and foreign-business citations refuse and roll back message and grant (33.696575ms)
✔ cross-role sends still need a matching resolved decision and consume it once (51.512186ms)
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (140.146767ms)
✔ startup token refusal returns safe code without value or partial listening broker (165.369856ms)
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (138.944712ms)
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (186.620794ms)
✔ trusted host registers later launches; socket clients never have a registration verb (143.38202ms)
✔ runtime excludes declared project roots even when host supplies no repoRoots (181.87432ms)
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (81.246986ms)
✔ v3b prototype refusals, views and append-only mutations (169.826249ms)
✔ gated approval authorizes once, survives store reopen, and fresh approval works (66.31408ms)
✔ another run cannot consume an approval; a failed check leaves it usable (170.806451ms)
✔ two scheduled callers have exactly one grant and one consumed refusal (35.10518ms)
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (71.095234ms)
✔ class drift gated to cross-role refuses before consumption (41.943308ms)
✔ class drift cross-role to gated refuses before consumption (42.424786ms)
✔ class drift gated to within-role refuses before consumption (30.638739ms)
✔ class drift cross-role to within-role refuses before consumption (32.766764ms)
✔ class drift within-role to gated refuses before consumption (36.477882ms)
✔ class drift within-role to cross-role refuses before consumption (41.694199ms)
✔ message.send consumes approval and prevents a later send or authorize (36.671986ms)
✔ role.revoke consumes approval and prevents a later revoke or authorize (38.981706ms)
✔ creates private WAL store and excludes a second writer until explicit close (32.015978ms)
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (31.440407ms)
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (30.603692ms)
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (197.089583ms)
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (24.893809ms)
✔ async transactions refuse before invoking their function (24.126915ms)
✔ recordTask keeps sync reads and a role write apart (50.271252ms)
✔ read_at must be one canonical UTC format, so the projection compares strings safely (18.59157ms)
✔ a bad entry refuses the whole record (28.135164ms)
✔ taskView reads the projection for one business (155.463221ms)
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (64.058729ms)
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (308.778218ms)
✔ without an adapter the server refuses every task verb (42.008397ms)
✔ the runtime refuses an invalid adapter and closes a valid one (52.987183ms)
✔ the process loads the S3 adapter from plain-data trackers (288.225403ms)
✔ socket capability stamps launch identity; shared views use wire, no SQL client (51.411447ms)
✔ two wire claims serialize; a lost reply never automatically retries (185.785126ms)
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (33.328628ms)
✔ client preserves UTF-8 when a response divides a multibyte character (23.91873ms)
✔ committed mutation followed by dropped reply reports unknown and is never retried (27.83698ms)
ℹ tests 81
ℹ suites 0
ℹ pass 81
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1202.758647
@@ -0,0 +1,68 @@
✔ config directory and file path follow MOSAIC_CONFIG (3.499142ms)
✔ the fixture business validates and comes back frozen (8.200955ms)
✔ two instances may share a definition (2.889005ms)
✔ top-level refusals (9.161253ms)
✔ arbiters and projects (18.302779ms)
✔ role instances (7.565324ms)
✔ Vikunja bots (14.375041ms)
✔ a role without Vikunja takes no tracker block (7.070399ms)
✔ credential references match the definition's services (8.726652ms)
✔ launch (19.472588ms)
✔ loadBusiness: file checks (4.217411ms)
✔ loadBusiness: not a regular file (75.414373ms)
✔ loading writes nothing (3.026667ms)
✔ names that are Object.prototype properties don't count as declared (6.226665ms)
✔ the shipped example refuses as written and validates once filled in (1.219599ms)
✔ usage errors exit 4 (477.425638ms)
✔ validate: a good business exits 0 and prints instance digests (122.226689ms)
✔ validate: project files (587.623581ms)
✔ validate: missing files and a broken system config (412.505364ms)
✔ validate: credential reference problems exit 2 and name each one (114.443595ms)
✔ validate: a token file inside the repository is refused (115.872029ms)
✔ validate: role definitions come from MOSAIC_ROLES_DIR (395.576524ms)
✔ resolve: prints one instance's record (382.072144ms)
✔ resolve: refusals (671.297775ms)
✔ parse: exactly one of file or env, plus the service's date (5.20369ms)
✔ check: a good file has no problems (3.750629ms)
✔ check never opens the file: a write-only token passes (0.883724ms)
✔ check: file problems (2.127762ms)
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (1.608643ms)
✔ check: dates and environment references (1.54162ms)
✔ path and load (3.887149ms)
✔ refusals (3.166373ms)
✔ systemVars flattens the validated config (3.977377ms)
✔ precedence: system, business, project, project role, agent (9.369246ms)
✔ limits narrow the definition and never widen it (5.814447ms)
✔ role.launch stays within-role only for the instance the launch block names (9.938752ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (3.698816ms)
✔ limits.authority narrows cross-role actions too (3.358156ms)
✔ classify (2.651364ms)
✔ the record carries what the broker and launcher need (1.878428ms)
✔ digest: key order doesn't matter, any value change does (16.959853ms)
✔ refusals (5.200785ms)
✔ the four shipped version 2 roles load (6.343277ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.898026ms)
✔ shipped authority follows the note's table (0.923704ms)
✔ version 1 files keep loading with no authority (1.694498ms)
✔ the conductor policy isn't a role (0.391224ms)
✔ a missing role file is exit 4, a symbolic link too (1.024239ms)
✔ version 2 refusals (2.475814ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (4.492413ms)
✔ credentials: Gitea scopes (2.331614ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (4.431165ms)
✔ credentials: services (1.431893ms)
✔ contract: a non-empty regular Markdown file beside the role file (1.71708ms)
✔ every key names known layers and a merge rule (1.619414ms)
✔ unknown keys and wrong layers refuse (1.319144ms)
✔ types (3.666892ms)
✔ merge: defaults, then the most specific layer wins (0.481573ms)
✔ merge: limits only narrow, and provenance lists each source (0.550264ms)
✔ merge doesn't change its inputs (0.231736ms)
ℹ tests 60
ℹ suites 0
ℹ pass 60
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3407.146697
+122
View File
@@ -0,0 +1,122 @@
✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (2575.437868ms)
✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (121.840489ms)
✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (259.988312ms)
✔ NO_COLOR: no escape codes, except reverse video on the selected row (583.254542ms)
✔ 1-9 shows the decide flow's own lines and question; only y resolves (75.499086ms)
✔ the app shows the same decide text as mosaic decide (31.276476ms)
✔ any key but y or Y at the question answers No; nothing is sent (246.91868ms)
✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (82.445862ms)
✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (25.837195ms)
✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (445.758625ms)
✔ quitting at the question answers No and sends nothing, armed or not (48.00947ms)
✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (37.142973ms)
✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (46.730692ms)
✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (367.441442ms)
✔ at every size the decide box shows each line y depends on, or says it is hidden (13172.79353ms)
✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (81.404171ms)
✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (37.386594ms)
✔ keys: views, moving, trail of a selection, the command line, help and quit (90.340526ms)
✔ one option: the status line says Press 1, not 1-1 (24.889131ms)
✔ no business: the app says so and :business picks one (15.041507ms)
✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (5.682261ms)
✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (230.572504ms)
✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.620902ms)
✔ decide with --note still asks; a No sends nothing (11.58382ms)
✔ the app's business comes from a live bus host only (2.054439ms)
✔ runApp leaves the terminal as it found it on q, end of input, SIGTERM, SIGINT, SIGHUP and an error, the first frame's included (10.430805ms)
✔ draw is pure: same state, same frame (1.022236ms)
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (54.444537ms)
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (50.898151ms)
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (39.078896ms)
✔ decide prints a declining choice as declining (30.366338ms)
✔ an unknown outcome is reported once and never resent (50.934024ms)
✔ a decision closed before the answer arrives exits 2 and points at its trail (25.95427ms)
✔ a prefix that matches two open decisions exits 2 and resolves neither (35.958621ms)
✔ without --business a command uses the live host's business, and a stale host.json is not a host (16.387693ms)
✔ every human command refuses inside an agent run before it touches the bus (26.398696ms)
✔ usage errors exit 4; no business and no host is a usage error (22.963519ms)
✔ agents and tasks print through the broker (21.444282ms)
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (3.686442ms)
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (126.715818ms)
✔ trackers come from the tracker.* variables of the one project that names a tracker project (178.667084ms)
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (76.289126ms)
✔ two projects that each name a tracker project refuse, since the boot shape holds one (103.153178ms)
✔ a business without tracker.baseUrl gets no trackers entry (93.104531ms)
✔ an unknown business and a broken system config refuse with exit 3 (220.550038ms)
✔ empty views say so (1.445675ms)
✔ the trail keeps the broker's order and names a decision's task without its rows (1.62635ms)
✔ tasks print the tracker fields the snapshot carries (0.275421ms)
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1234.573704ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (255.78367ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (125.006311ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1130.72943ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (307.7752ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (200.138306ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (265.698781ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (181.266956ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (146.196068ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (194.368599ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (114.904121ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (216.017254ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (34.650091ms)
✔ bus stop refuses to signal a live pid that is not a bus host (204.737058ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (402.133502ms)
✔ bus start refuses with exit 3 without a notifier config (193.518643ms)
✔ bus start runs until bus stop; status reports it while it runs (938.99266ms)
✔ bus-service.sh renders the unit and installs it into a given directory (78.270678ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (121.526952ms)
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1386.034158ms)
✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (583.053034ms)
✔ a runner that stops at once ends its launch with the runner's reason (276.596415ms)
✔ 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 (902.684414ms)
✔ 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 (3857.50546ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (110.075476ms)
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (123.391452ms)
✔ a launch client that never closes its side doesn't hold the host's close (125.094283ms)
✔ a runner that ignores SIGTERM is killed when the host closes (1230.598399ms)
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (7.358594ms)
✔ zoned uses the IANA zone across DST (29.221001ms)
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (41.382705ms)
✔ two blocking decisions get two DMs with different nonces (41.511473ms)
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.391142ms)
✔ a failed DM is journaled, backs off, and is retried until it lands (41.061189ms)
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (56.151739ms)
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (83.90553ms)
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (37.52893ms)
✔ 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 (232.090057ms)
✔ a restart after the second refusal does not send before that refusal's 30 min are up (113.374042ms)
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (36.376926ms)
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (39.173684ms)
✔ an inbox read failure is logged and the next poll retries (2.215435ms)
✔ no Discord id reaches the journal or the log (22.25059ms)
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (12.880256ms)
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (6.917527ms)
✔ the journal: a whole file that is one torn line truncates to empty (2.277526ms)
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.171449ms)
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (3.129807ms)
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (8.844715ms)
✔ the journal: a symlinked directory refuses and says it is a link (1.039649ms)
✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (1.847472ms)
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (1.433451ms)
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (1.114175ms)
✔ digest content stays within Discord's 2000 characters (0.621691ms)
✔ runLoop never overlaps ticks and stops after the one in flight (119.237271ms)
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 (724.50701ms)
✔ the transport writes {business, verb, args} to the child and reads its JSON (91.241323ms)
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2435.284197ms)
✔ busExit and refuseInsideAgent (0.695552ms)
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (115.335304ms)
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1053.430583ms)
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (15.844796ms)
✔ launches off and on go to the broker and change the business's launch state (14.087384ms)
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (15.217499ms)
ℹ tests 112
ℹ suites 0
ℹ pass 112
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 18837.646924
@@ -0,0 +1 @@
2026-10-11T23:14:34Z
@@ -0,0 +1,66 @@
✔ sessionModel: agent vars win, then the system's execution settings (28.582565ms)
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (10.80112ms)
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (7.891606ms)
✔ a bundle is written once: an existing file refuses (4.681172ms)
✔ a path with a single quote can't go into the hook command (5.835675ms)
✔ allow exits 0, a deny exits 2 with the reason on stderr (491.930816ms)
✔ a missing or wrong policy, or a bad event, exits 2 (280.01237ms)
✔ the bundle's wrapped command: a missing gate or node still blocks (1240.222858ms)
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (1816.377717ms)
✔ claude: the hook alone blocks a path outside the workspace (731.111927ms)
✔ claude: a second turn resumes the first turn's session (1803.113028ms)
✔ claude adapter: --restricted is always passed (9.598654ms)
✔ claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable (43.271331ms)
✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (1233.642263ms)
✔ claude: a missing hook or MCP file refuses before claude starts (13.803182ms)
✔ pi: policy tools and typed tools pass, anything else is blocked (9.070302ms)
✔ claude: builtins map from pi names, typed tools need the mcp prefix (1.487225ms)
✔ file tool paths must resolve inside the workspace (7.762967ms)
✔ pi's own path normalisation can't be used to step out (5.452659ms)
✔ a symlink inside the workspace that points out is outside (2.168089ms)
✔ a dangling symlink is refused at any depth, in both harnesses (8.67524ms)
✔ read is checked under every spelling pi's read would open, in both harnesses (45.396712ms)
✔ other spellings cover directories, dangling links and pi's cwd (3.008336ms)
✔ a relative path climbs from the workspace's real path, in both harnesses (10.755171ms)
✔ claude path fields per tool (1.281399ms)
✔ glob patterns stay inside the workspace (1.64637ms)
✔ a path that can't be checked is blocked (0.661031ms)
✔ initialize, ping and tools/list (100.93379ms)
✔ tools/call goes through the tool socket; a refusal is an isError result (79.489337ms)
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (73.485687ms)
✔ a missing argument is a usage error (131.29632ms)
✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (1126.108528ms)
✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (674.432288ms)
✔ pi: a read is refused when pi would open another spelling outside (584.730201ms)
✔ pi: a relative path climbs from the real path of a workspace behind a symlink (675.898409ms)
✔ pi: a relative path climbs from the real path of a dataRoot behind a symlink (959.55437ms)
✔ pi: a missing extension refuses before any model call (14.697573ms)
✔ pi: an extension without its configuration fails pi's start (532.20757ms)
✔ founderCheck: founder variables, then a needed service without a usable token (5.252564ms)
✔ turnRequest names the sender, class, reply and decision (0.404605ms)
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (437.11389ms)
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (195.724419ms)
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (575.474458ms)
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1375.381179ms)
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (194.928324ms)
✔ founder credentials stop before the claim (20) (184.605222ms)
✔ a refused claim exits 21; an ended run's capability exits 22 (453.390314ms)
✔ the launch ending under a running session exits 22 (217.268639ms)
✔ a broker that stays unreachable exits 23 after brokerRetries polls (272.988015ms)
✔ a broker that is down at the claim exits 23, not 21 (96.658628ms)
✔ no capability, or a malformed one, on stdin exits 2 (289.734618ms)
✔ a missing or malformed policy exits 2 before the claim (164.918924ms)
✔ the PM gets launch, its task verbs and the reads (11.769269ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (4.543725ms)
✔ a coder with a tracker gets its own task tools and no task_reassign (2.885134ms)
✔ launch only when the business's launch block names the instance as launcher (2.70311ms)
✔ an action outside the instance's authority has no tool (8.068441ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (24.367924ms)
ℹ tests 58
ℹ suites 0
ℹ pass 58
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 11062.352505
@@ -0,0 +1,35 @@
✔ resolveSeat: by name under --repo resolves the repo layout (2.571856ms)
✔ resolveSeat: by path resolves the fleet layout (0.733308ms)
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (1.502708ms)
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (1.320435ms)
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (1.629035ms)
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (2.372825ms)
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.798218ms)
✔ CLI launch: registers, execs the fake launch script, and passes args through (64.567607ms)
✔ CLI launch: --harness lands in the record (57.869902ms)
✔ CLI launch: the launch script's own exit code passes through (43.095625ms)
✔ CLI launch: relaunching a seat rewrites the one registration record (93.424815ms)
✔ CLI launch: omitting --task records an empty string, not null (46.406032ms)
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (133.037855ms)
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (204.105953ms)
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.619616ms)
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.743583ms)
✔ 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 (1.034ms)
✔ updateTask: records setBy, preserves startedAt and every unrelated field, upgrades an old record in place only when the task is set, and replaces a previous attribution (1.309264ms)
✔ 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 (424.850272ms)
✔ family: exactly one launch.max key in the model name, else null (1.134819ms)
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (1.321324ms)
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (2.696089ms)
✔ session file and launch log: 0600, the session file written once (1.648677ms)
✔ endReason maps the runner's exit codes; a signal is killed (0.21076ms)
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.820038ms)
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (1.401457ms)
✔ a session runs under unshare as pid 1 of its namespace, claims, answers, and stops on mosaic stop (250.310689ms)
ℹ tests 27
ℹ suites 0
ℹ pass 27
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1192.412558
@@ -0,0 +1 @@
2026-10-11T23:09:50Z
@@ -0,0 +1,16 @@
tasks rc=0 ℹ pass 71 ℹ fail 0 ℹ skipped 0
bus rc=0 ℹ pass 81 ℹ fail 0 ℹ skipped 0
business rc=0 ℹ pass 60 ℹ fail 0 ℹ skipped 0
harness rc=0 ℹ pass 58 ℹ fail 0 ℹ skipped 0
cli rc=0 ℹ pass 112 ℹ fail 0 ℹ skipped 0
webui rc=0 ℹ pass 50 ℹ fail 0 ℹ skipped 0
seat rc=0 ℹ pass 27 ℹ fail 0 ℹ skipped 0
test-auth rc=0 selftest: 15 passed, 0 failed
test-conductor rc=0 selftest: 17 passed, 0 failed
test-config rc=0 selftest: 24 passed, 0 failed
test-discord rc=0 discord suite: 66 passed, 0 failed
test-extension-package rc=0 extension package selftest: 18 passed, 0 failed
test-foundation rc=0 selftest: 44 passed, 0 failed
test-queue rc=0 queue suite: 27 passed, 0 failed
test-release rc=0 selftest: 14 passed, 0 failed
test-task rc=0 selftest: 98 passed, 0 failed
@@ -0,0 +1,79 @@
✔ the boot config is checked before anything starts (47.200233ms)
✔ a business with no tracker entry refuses task verbs (26.054232ms)
✔ credential.expiring and .expired are recorded once per instance (71.464071ms)
✔ a token file that changes on disk records credential.changed (19.23182ms)
✔ autostart polls, reconciles and retries a startup the tracker was down for (55.654321ms)
✔ a refusal a restart must clear is not retried by the poll (19.019302ms)
✔ a poll that fires while two are queued is dropped (31.522387ms)
✔ close waits for a running verb and refuses one that has not started (84.044017ms)
✔ the bundled Vikunja is the pinned upstream image the runbook names (1.460154ms)
✔ every published port is on 127.0.0.1, and no secret is in the file (0.553947ms)
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (224.349567ms)
✔ the project list holds recorded project objects in the recorded list envelope (22.371425ms)
✔ the recorded task bodies pass the checks S3 applies to every read (1.419284ms)
✔ the client works against the fake over real HTTP with the platform fetch (215.956568ms)
✔ a correct install starts, and the first reconcile records tasks that already exist (56.215912ms)
✔ verbs refuse while a business is starting and after startup refused it (41.960644ms)
✔ startup refuses a token that can do more than its role needs (69.306729ms)
✔ startup refuses an unsupported version and flags an untested one (63.000628ms)
✔ startup refuses a board that the runbook did not install (85.304122ms)
✔ startup refuses a project the sync bot cannot read (46.47812ms)
✔ startup refuses a configured label the pm bot cannot see (25.371916ms)
✔ startup refuses an expired credential and a missing sync credential (52.17586ms)
✔ an unreachable tracker refuses with tracker-unavailable (15.114537ms)
✔ an edit in the UI is recorded once, with the fields that changed (127.212799ms)
✔ a move between open buckets is seen on the board, though updated does not change (65.945293ms)
✔ a person's comment is counted and a bot's is not (118.78426ms)
✔ the hourly reconcile catches a comment through comment_count (159.932149ms)
✔ a task closed in the UI leaves the open view with its done bucket (177.635334ms)
✔ a task that leaves the board is recorded as deleted, moved or out of reach (196.943355ms)
✔ a poll that read before a verb wrote does not overwrite the verb (89.064442ms)
✔ a tracker fault during a tick is reported and the next tick catches up (53.781773ms)
✔ a malformed answer refuses the tick with tracker-shape (22.569344ms)
✔ no token value reaches the database, the log or a refusal (71.43717ms)
✔ the first look at a task counts only comments inside the window (47.402383ms)
✔ with descendants off the adapter reads no project list and no child, as in v0 (75.132178ms)
✔ a three-level tree is read per project, and a project outside it is not (43.731266ms)
✔ a watch-only child keeps the business ready, and a failed child read refuses as transient (52.398292ms)
✔ a root without the installed buckets refuses with descendants on (20.347468ms)
✔ the project list refuses without read_all, and skips archived projects and saved filters (47.255975ms)
✔ descendants must be true or false (15.341385ms)
✔ a move inside the tree is a moved tombstone plus a snapshot under the new ref (111.510521ms)
✔ a child made after startup joins the tree at the next reconcile (86.324892ms)
✔ a child that leaves the tree has its tasks recorded missing, and the ledger keeps their history (94.225955ms)
✔ a child deleted or hidden between reconciles drops out at the next tick, and the root still refuses (146.919538ms)
✔ a reconcile that fails keeps the previous tree (80.42665ms)
✔ a closed task the cursor missed takes its own project's done bucket (61.092169ms)
✔ closed tasks read at startup take their own project's done bucket (38.269422ms)
✔ a closed task the cursor returns takes its own project's done bucket (70.162556ms)
✔ a task moved out of a child that leaves the tree at the same time is moved, not no-access (46.700813ms)
✔ a task in a bucket outside the five keeps its raw bucket id (38.564246ms)
✔ bucket 0 on a watch-only task never becomes a bucket or a state (95.280726ms)
✔ a child whose kanban view is deleted still refuses the tick, and the reconcile reads it watch-only (70.345835ms)
✔ a child dropped at a tick comes back at the next reconcile once the bot reads it again (50.218065ms)
✔ task.create needs a recorded human request and a requirement id (95.495374ms)
✔ only labels named in the business file can be written (54.586778ms)
✔ task.schedule sets and clears a due date and relations (87.727746ms)
✔ assign and reassign move the role bots and record task.assigned (136.022256ms)
✔ task.update.assigned is for the assignee and records task.state (158.096097ms)
✔ a wrong expected digest records task.conflict and writes nothing (58.524297ms)
✔ a cross-role verb needs a resolved decision, used once (75.182705ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (157.320621ms)
✔ a lost answer is settled by a re-read and never retried (77.393226ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (77.31654ms)
✔ a task the sync bot cannot read refuses and records nothing (36.749543ms)
✔ verbs and polls for one business run one at a time (113.82165ms)
✔ a due date with milliseconds is written to the second (80.352537ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (47.226617ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (27.798719ms)
✔ a create whose final read fails succeeds and records task.created (44.517445ms)
✔ an edit between the last write and the final read shows as external on the next poll (70.754281ms)
✔ task.created is recorded when a later label write fails (24.516626ms)
ℹ tests 71
ℹ suites 0
ℹ pass 71
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1636.446672
@@ -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
scripts/test-extension-package.sh: line 14: 231435 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/scratch/rocko-r73) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
queue suite: 27 passed, 0 failed
@@ -0,0 +1,16 @@
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
OK status safe on empty state (exit 0)
OK status created no pointer
OK fault-injected activation refuses (exit 1)
OK refused activation wrote no pointer
OK refusal logged exactly once with valid fields
OK healthy activation succeeds (exit 0)
OK pointer written with valid fields
OK repeat activation succeeds (log grows) (exit 0)
OK log is append-only across activations
OK rollback without previous refuses (exit 1)
selftest: 14 passed, 0 failed
@@ -0,0 +1,101 @@
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)
OK mock adapter: gate passes on matching mock response (exit 0)
OK mock adapter: expect-mismatch recorded (exit 1)
OK mismatch reason recorded
OK unknown adapter fails closed (exit 1)
OK mission task runs via mock adapter (exit 0)
OK mission section injected into generated prompt
OK retry of mission run succeeds (exit 0)
OK retriedFrom lineage recorded
OK mission section present after retry (relative path resolved)
OK retry of missing run exits 4 (exit 4)
OK skill install bundled ms-tools (exit 0)
OK installed to skills-available
OK double install refuses (exit 1)
OK activate enables skill (exit 0)
OK enabled dir populated
OK uninstall while enabled refuses (exit 1)
OK deactivate moves back to available (exit 0)
OK uninstall removes from available (exit 0)
OK seat with enabled skill launches
OK skill path delivered to adapter
OK shipped researcher contract resolves (ceiling + network)
OK resolve-role refuses a non-role document (conductor policy) (exit 2)
OK resolve-role refuses name/filename mismatch (exit 2)
OK resolve-role refuses unknown network declaration (exit 2)
OK resolve-role refuses duplicate role tool (exit 2)
OK resolve-role refuses unsupported tool (exit 2)
OK resolve-role refuses missing contract file (exit 4)
OK version 1 role prints no contract line
OK shipped version 2 roles resolve with their contracts (pm, cto, coder, reviewer)
OK resolve-role accepts a minimal version 2 role (exit 0)
OK resolve-role refuses an action outside the vocabulary (exit 2)
OK resolve-role refuses a gated-only action in a role file (exit 2)
OK resolve-role refuses a Vikunja verb no role may hold (exit 2)
OK resolve-role refuses a version 2 role whose contract is missing (exit 2)
OK resolve-role refuses a contract that is a symbolic link (exit 2)
OK seat launches under role ceiling
OK role ceiling narrows seat tools (read,write,bash -> read,bash)
OK narrowing recorded loudly
OK missing role contract refuses launch (exit 2)
OK missing-contract refusal names the role
OK empty ceiling intersection -> tool-free seat
OK tool-free outcome recorded loudly
OK overridden agents dir without seat definition refuses (exit 4)
OK seat-resolution refusal names the seat
OK policy: mission only -> mission tools
OK policy: task only -> task tools
OK policy: both -> intersection (task narrowed)
OK policy: empty intersection -> tool-free
OK invalid mission capabilities rejected (exit 2)
mosaic-task: unsupported mission tool: "sudo" (supported: read, write, edit, bash, grep, find, ls)
OK task run with user layer present (exit 0)
OK user context dispatched into generated prompt
OK workspace+tools task runs via mock (exit 0)
OK workspace path + tools delivered to adapter
OK persistent workspace created on host
OK plain task still runs (no workspace/tools) (exit 0)
OK workspace var present but empty when absent
OK tools empty when absent
OK unknown tool exits 2 (exit 2)
OK workspace traversal exits 2 (exit 2)
OK live hello task succeeds with exact marker
OK result.json written in run dir
OK result.json contents are correct
OK wrong expectExact fails with exit 1 (reason: expect-mismatch)
OK each run gets a distinct run dir (no clobber)
OK list shows both runs
OK fork base: teach succeeds (exit 0)
OK fork child recalls ancestor context (exit 0)
OK forked child recalled ancestor code word
OK ancestor session untouched by fork
OK child session dir has its own branch file
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
OK user recall run succeeds
OK recalled user name (response: Jason)
OK no agent identity on headless run
selftest: 98 passed, 0 failed
@@ -0,0 +1,59 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (8106.572334ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (8747.634305ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (9887.359002ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (264.222307ms)
✔ decisions route serves the roles and recent lists from the reader view (109.024685ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (108.781146ms)
✔ bus refusals map to statuses and never carry a path or a stack (82.034912ms)
✔ humanCall speaks the human transport protocol without blocking the server (2363.712435ms)
✔ busReader takes --business, else the live bus host, else refuses (837.234229ms)
✔ humanCall kills a transport that runs past its timeout (595.698952ms)
✔ serve refuses a --business value that is not a business id (1239.153702ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (7658.493085ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2334.361368ms)
✔ conversation view: a newer session with no readable history keeps the marker (1475.86245ms)
✔ conversation view: seats without history say so and offer no reply (1154.68976ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (6505.513225ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (57598.455344ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.787175ms)
✔ credential state from the date alone, as the bus states it (0.265543ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2447.752007ms)
✔ queue: a refused read fails closed with the store's text, redacted (1106.728316ms)
✔ queue: a store that fails to load, or a child that dies, gives a fixed message with no path or stack (2952.199165ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (43.48209ms)
✔ business: missing or invalid file refuses with the module's text, redacted (18.270212ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (23.291575ms)
✔ settings: release, board, loopback address, notifier binding name only (657.992106ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (7673.38997ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (26157.798956ms)
✔ loopback host and board origin fail closed (12.188559ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (347.900023ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (276.644747ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1741.484661ms)
✔ render prints the unit without the preview flags (60.324721ms)
✔ render carries every serve flag, the preview set included (85.287597ms)
✔ a partial preview set is refused (184.902743ms)
✔ a value the unit cannot carry is refused, for every flag (3027.921779ms)
✔ the rendered unit names none of the human proof agent markers and sets no agent variable (130.32085ms)
✔ install into --dir with --no-reload writes the file whole, then reports unchanged (738.374793ms)
✔ install without --no-reload runs daemon-reload and nothing that starts the unit (114.539901ms)
✔ uninstall refuses while the unit is active, and removes it once it is not (669.457415ms)
✔ status prints the unit state and GET /healthz on the installed port (700.129598ms)
✔ usage: an unknown verb or flag, and start or enable, exit 4 (267.924672ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.609561ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (3.068117ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.504909ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (12906.255137ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2975.960387ms)
✔ Queue, Business and Settings in their five states, with no move button (10184.597385ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2343.523176ms)
✔ the server log names no config path when the system config refuses (580.596691ms)
ℹ tests 50
ℹ suites 0
ℹ pass 50
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 58347.310973
@@ -0,0 +1,6 @@
baseline: pass
role-keeps-reassign: NOT APPLIED (sed matched nothing)
broker-unlisted-cross: SURVIVED
resolve-cross-unnarrowed: killed (✖ an action outside the instance's authority has no tool;✖ limits.authority narrows cross-role actions too;)
classify-unlisted-cross: killed (✖ classify;✖ limits.authority narrows cross-role actions too;✖ limits.authority without role.launch leaves the launcher with no launch block;✖ limits narrow the definition and never widen it;✖ role.launch stays within-role only for the instance the launch block names;)
tools-all-task-verbs: killed (✖ an action outside the instance's authority has no tool;)
@@ -0,0 +1,67 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (41.10989ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (30.340056ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (27.301914ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (16.784646ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (16.117365ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (16.007796ms)
✔ task action subjects and linked decision trail are complete and ordered (33.402123ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (12.499912ms)
✔ business isolation includes inherited object names and cross-business message references (19.585889ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (27.095845ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (12.374754ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (21.007289ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (14.896337ms)
✔ both arbiters require human resolution when their cross-role route is themselves (21.130329ms)
✔ systemVars flattens the validated config (3.298819ms)
✔ precedence: system, business, project, project role, agent (9.974934ms)
✔ limits narrow the definition and never widen it (4.037503ms)
✔ role.launch stays within-role only for the instance the launch block names (10.06119ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (3.498072ms)
✔ limits.authority narrows cross-role actions too (1.527146ms)
✔ classify (1.816472ms)
✔ the record carries what the broker and launcher need (1.558999ms)
✔ digest: key order doesn't matter, any value change does (10.199487ms)
✔ refusals (4.176465ms)
✔ the four shipped version 2 roles load (14.372215ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.984217ms)
✔ shipped authority follows the note's table (1.084755ms)
✔ version 1 files keep loading with no authority (2.030792ms)
✔ the conductor policy isn't a role (0.422745ms)
✔ a missing role file is exit 4, a symbolic link too (0.604689ms)
✔ version 2 refusals (2.11256ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (3.892762ms)
✔ credentials: Gitea scopes (1.509172ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (2.083022ms)
✔ credentials: services (0.971694ms)
✔ contract: a non-empty regular Markdown file beside the role file (1.069304ms)
✔ the PM gets launch, its task verbs and the reads (10.141611ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (3.092654ms)
✔ launch only when the business's launch block names the instance as launcher (3.195221ms)
✔ an action outside the instance's authority has no tool (2.493976ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (17.882552ms)
✔ task.create needs a recorded human request and a requirement id (62.327198ms)
✔ only labels named in the business file can be written (37.091411ms)
✔ task.schedule sets and clears a due date and relations (44.451536ms)
✔ assign and reassign move the role bots and record task.assigned (66.030754ms)
✔ task.update.assigned is for the assignee and records task.state (66.783287ms)
✔ a wrong expected digest records task.conflict and writes nothing (27.559383ms)
✔ a cross-role verb needs a resolved decision, used once (41.3223ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (39.501253ms)
✔ a lost answer is settled by a re-read and never retried (48.948392ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (45.537621ms)
✔ a task the sync bot cannot read refuses and records nothing (24.722852ms)
✔ verbs and polls for one business run one at a time (107.598043ms)
✔ a due date with milliseconds is written to the second (99.477546ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (51.18486ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (30.29597ms)
✔ a create whose final read fails succeeds and records task.created (43.712142ms)
✔ an edit between the last write and the final read shows as external on the next poll (60.844534ms)
✔ task.created is recorded when a later label write fails (43.90553ms)
ℹ tests 59
ℹ suites 0
ℹ pass 59
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1118.992245
@@ -0,0 +1,11 @@
--- /mnt/storage/scratch/rocko-r73-out/mutants-base/orig 2026-10-11 18:09:25.811161905 -0500
+++ packages/bus/src/broker.mjs 2026-10-11 18:09:27.269340616 -0500
@@ -274,7 +274,7 @@
? 'within-role'
: a?.crossRole.includes(action)
? 'cross-role'
- : 'gated';
+ : 'cross-role';
}
#decision(s, id) {
const d = this.#store.get('SELECT * FROM decisions WHERE id=? AND business=?', id, s.business);
@@ -0,0 +1,67 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (52.058368ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (31.827945ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (32.343445ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (15.453301ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (16.112223ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (15.465475ms)
✔ task action subjects and linked decision trail are complete and ordered (34.817745ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (11.489008ms)
✔ business isolation includes inherited object names and cross-business message references (25.296217ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (31.184317ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (16.792175ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (22.551334ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (13.915971ms)
✔ both arbiters require human resolution when their cross-role route is themselves (23.619893ms)
✔ systemVars flattens the validated config (3.200801ms)
✔ precedence: system, business, project, project role, agent (7.711976ms)
✔ limits narrow the definition and never widen it (5.662494ms)
✔ role.launch stays within-role only for the instance the launch block names (11.879594ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (4.345739ms)
✔ limits.authority narrows cross-role actions too (1.909662ms)
✔ classify (2.20486ms)
✔ the record carries what the broker and launcher need (2.46906ms)
✔ digest: key order doesn't matter, any value change does (16.682292ms)
✔ refusals (3.683678ms)
✔ the four shipped version 2 roles load (5.36865ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.795612ms)
✔ shipped authority follows the note's table (1.155071ms)
✔ version 1 files keep loading with no authority (1.552767ms)
✔ the conductor policy isn't a role (0.342392ms)
✔ a missing role file is exit 4, a symbolic link too (0.602196ms)
✔ version 2 refusals (2.146247ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (3.688492ms)
✔ credentials: Gitea scopes (2.236086ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (2.557229ms)
✔ credentials: services (0.936797ms)
✔ contract: a non-empty regular Markdown file beside the role file (1.47412ms)
✔ the PM gets launch, its task verbs and the reads (11.865141ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (4.766334ms)
✔ launch only when the business's launch block names the instance as launcher (3.204748ms)
✔ an action outside the instance's authority has no tool (2.992598ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (16.830589ms)
✔ task.create needs a recorded human request and a requirement id (67.154682ms)
✔ only labels named in the business file can be written (33.442526ms)
✔ task.schedule sets and clears a due date and relations (48.841556ms)
✔ assign and reassign move the role bots and record task.assigned (78.987774ms)
✔ task.update.assigned is for the assignee and records task.state (65.470032ms)
✔ a wrong expected digest records task.conflict and writes nothing (32.691954ms)
✔ a cross-role verb needs a resolved decision, used once (37.492507ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (45.183086ms)
✔ a lost answer is settled by a re-read and never retried (66.58075ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (47.459336ms)
✔ a task the sync bot cannot read refuses and records nothing (24.247305ms)
✔ verbs and polls for one business run one at a time (102.11683ms)
✔ a due date with milliseconds is written to the second (77.242581ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (48.960103ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (20.384311ms)
✔ a create whose final read fails succeeds and records task.created (40.019605ms)
✔ an edit between the last write and the final read shows as external on the next poll (48.872647ms)
✔ task.created is recorded when a later label write fails (28.904685ms)
ℹ tests 59
ℹ suites 0
ℹ pass 59
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1099.85091
@@ -0,0 +1,9 @@
--- /mnt/storage/scratch/rocko-r73-out/mutants-base/orig 2026-10-11 18:09:25.812634433 -0500
+++ packages/business/src/resolve.mjs 2026-10-11 18:09:29.450323415 -0500
@@ -100,5 +100,5 @@
if (!ACTIONS.includes(action)) refuse(`unknown action: ${JSON.stringify(action)}`);
if (resolved.limits.authority.withinRole.includes(action)) return "within";
if (resolved.limits.authority.crossRole.includes(action)) return "cross";
- return "gated";
+ return "cross";
}
@@ -0,0 +1,174 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (34.562762ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (48.881252ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (31.40005ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (19.163154ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (18.637808ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (23.106973ms)
✔ task action subjects and linked decision trail are complete and ordered (33.924385ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (12.548753ms)
✔ business isolation includes inherited object names and cross-business message references (18.991923ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (21.671041ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (12.2862ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (22.485066ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (14.168727ms)
✔ both arbiters require human resolution when their cross-role route is themselves (23.026ms)
✔ systemVars flattens the validated config (3.055626ms)
✔ precedence: system, business, project, project role, agent (7.376521ms)
✖ limits narrow the definition and never widen it (4.765621ms)
✖ role.launch stays within-role only for the instance the launch block names (5.845037ms)
✖ limits.authority without role.launch leaves the launcher with no launch block (2.382717ms)
✖ limits.authority narrows cross-role actions too (2.320376ms)
✖ classify (1.91099ms)
✔ the record carries what the broker and launcher need (1.859849ms)
✔ digest: key order doesn't matter, any value change does (16.863167ms)
✔ refusals (4.843702ms)
✔ the four shipped version 2 roles load (4.952568ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.790725ms)
✔ shipped authority follows the note's table (0.859956ms)
✔ version 1 files keep loading with no authority (1.580548ms)
✔ the conductor policy isn't a role (0.410542ms)
✔ a missing role file is exit 4, a symbolic link too (0.687476ms)
✔ version 2 refusals (2.208359ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (4.089476ms)
✔ credentials: Gitea scopes (1.522708ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.96425ms)
✔ credentials: services (1.096518ms)
✔ contract: a non-empty regular Markdown file beside the role file (1.388238ms)
✔ the PM gets launch, its task verbs and the reads (10.881554ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (4.91844ms)
✔ launch only when the business's launch block names the instance as launcher (2.979843ms)
✔ an action outside the instance's authority has no tool (2.821761ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (16.298799ms)
✔ task.create needs a recorded human request and a requirement id (77.423799ms)
✔ only labels named in the business file can be written (39.818018ms)
✔ task.schedule sets and clears a due date and relations (52.774716ms)
✔ assign and reassign move the role bots and record task.assigned (70.210371ms)
✔ task.update.assigned is for the assignee and records task.state (71.320218ms)
✔ a wrong expected digest records task.conflict and writes nothing (24.030222ms)
✔ a cross-role verb needs a resolved decision, used once (35.582912ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (33.762759ms)
✔ a lost answer is settled by a re-read and never retried (33.963244ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (49.791704ms)
✔ a task the sync bot cannot read refuses and records nothing (28.959016ms)
✔ verbs and polls for one business run one at a time (103.849164ms)
✔ a due date with milliseconds is written to the second (77.623434ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (41.880183ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (18.077862ms)
✔ a create whose final read fails succeeds and records task.created (38.538489ms)
✔ an edit between the last write and the final read shows as external on the next poll (50.303392ms)
✔ task.created is recorded when a later label write fails (21.206701ms)
ℹ tests 59
ℹ suites 0
ℹ pass 54
ℹ fail 5
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1016.65102
✖ failing tests:
test at packages/business/tests/resolve.test.mjs:69:1
✖ limits narrow the definition and never widen it (4.765621ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'cross' !== 'gated'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73-base/packages/business/tests/resolve.test.mjs:87: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: 'cross',
expected: 'gated',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/business/tests/resolve.test.mjs:91:1
✖ role.launch stays within-role only for the instance the launch block names (5.845037ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'cross' !== 'gated'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73-base/packages/business/tests/resolve.test.mjs:98: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: 'cross',
expected: 'gated',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/business/tests/resolve.test.mjs:112:1
✖ limits.authority without role.launch leaves the launcher with no launch block (2.382717ms)
AssertionError [ERR_ASSERTION]: agent
'cross' !== 'gated'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73-base/packages/business/tests/resolve.test.mjs:119:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 'cross',
expected: 'gated',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/business/tests/resolve.test.mjs:124:1
✖ limits.authority narrows cross-role actions too (2.320376ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'cross' !== 'gated'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73-base/packages/business/tests/resolve.test.mjs:130: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: 'cross',
expected: 'gated',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/business/tests/resolve.test.mjs:135:1
✖ classify (1.91099ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'cross' !== 'gated'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73-base/packages/business/tests/resolve.test.mjs:140: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: 'cross',
expected: 'gated',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,11 @@
--- /mnt/storage/scratch/rocko-r73-out/mutants-base/orig 2026-10-11 18:09:25.812634433 -0500
+++ packages/business/src/resolve.mjs 2026-10-11 18:09:28.444840244 -0500
@@ -65,7 +65,7 @@
if (vars["limits.network"]) network = narrowerNetwork(network, vars["limits.network"]);
if (vars["limits.authority"]) {
withinRole = withinRole.filter((a) => vars["limits.authority"].includes(a));
- crossRole = crossRole.filter((a) => vars["limits.authority"].includes(a));
+ crossRole = [...crossRole];
}
// role.launch needs the business file's launch block naming this
// instance (addendum A section 8), and the verb must survive
@@ -0,0 +1,127 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (32.184603ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (34.006489ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (38.37318ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (17.069074ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (18.894895ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (14.699724ms)
✔ task action subjects and linked decision trail are complete and ordered (34.571108ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (11.623471ms)
✔ business isolation includes inherited object names and cross-business message references (18.142067ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (23.301159ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (9.613466ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (19.23009ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (12.482528ms)
✔ both arbiters require human resolution when their cross-role route is themselves (19.85656ms)
✔ systemVars flattens the validated config (3.10758ms)
✔ precedence: system, business, project, project role, agent (8.111606ms)
✔ limits narrow the definition and never widen it (4.340763ms)
✔ role.launch stays within-role only for the instance the launch block names (7.7091ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (4.788773ms)
✖ limits.authority narrows cross-role actions too (4.913175ms)
✔ classify (2.31828ms)
✔ the record carries what the broker and launcher need (1.866404ms)
✔ digest: key order doesn't matter, any value change does (11.152959ms)
✔ refusals (4.229422ms)
✔ the four shipped version 2 roles load (4.850962ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.823772ms)
✔ shipped authority follows the note's table (0.832213ms)
✔ version 1 files keep loading with no authority (1.502179ms)
✔ the conductor policy isn't a role (0.338784ms)
✔ a missing role file is exit 4, a symbolic link too (0.574289ms)
✔ version 2 refusals (1.813974ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (3.519361ms)
✔ credentials: Gitea scopes (1.479096ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.986778ms)
✔ credentials: services (0.824537ms)
✔ contract: a non-empty regular Markdown file beside the role file (1.067344ms)
✔ the PM gets launch, its task verbs and the reads (12.570361ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (4.699967ms)
✔ launch only when the business's launch block names the instance as launcher (3.180079ms)
✖ an action outside the instance's authority has no tool (6.26463ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (16.396169ms)
✔ task.create needs a recorded human request and a requirement id (68.567729ms)
✔ only labels named in the business file can be written (37.772712ms)
✔ task.schedule sets and clears a due date and relations (42.822471ms)
✔ assign and reassign move the role bots and record task.assigned (65.432289ms)
✔ task.update.assigned is for the assignee and records task.state (62.852511ms)
✔ a wrong expected digest records task.conflict and writes nothing (28.459203ms)
✔ a cross-role verb needs a resolved decision, used once (28.527583ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (28.982719ms)
✔ a lost answer is settled by a re-read and never retried (30.392592ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (38.949964ms)
✔ a task the sync bot cannot read refuses and records nothing (25.968994ms)
✔ verbs and polls for one business run one at a time (99.920681ms)
✔ a due date with milliseconds is written to the second (69.877437ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (40.473302ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (16.49068ms)
✔ a create whose final read fails succeeds and records task.created (38.315534ms)
✔ an edit between the last write and the final read shows as external on the next poll (45.052755ms)
✔ task.created is recorded when a later label write fails (19.326478ms)
ℹ tests 59
ℹ suites 0
ℹ pass 57
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 943.487341
✖ failing tests:
test at packages/business/tests/resolve.test.mjs:124:1
✖ limits.authority narrows cross-role actions too (4.913175ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
{
crossRole: [
+ 'task.reassign',
'task.scope.change'
],
withinRole: [
'task.update.assigned',
'git.push.working'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73-base/packages/business/tests/resolve.test.mjs:129: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: { withinRole: [ 'task.update.assigned', 'git.push.working' ], crossRole: [ 'task.reassign', 'task.scope.change' ] },
expected: { withinRole: [ 'task.update.assigned', 'git.push.working' ], crossRole: [ 'task.scope.change' ] },
operator: 'deepStrictEqual',
diff: 'simple'
}
test at packages/harness/tests/tools.test.mjs:44:1
✖ an action outside the instance's authority has no tool (6.26463ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
+ 'task_reassign',
+ 'task_scope_change',
'raise_decision',
'list_agents',
'inbox',
'trail',
'list_tasks'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73-base/packages/harness/tests/tools.test.mjs:46: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: [ 'task_reassign', 'task_scope_change', 'raise_decision', 'list_agents', 'inbox', 'trail', 'list_tasks' ],
expected: [ 'raise_decision', 'list_agents', 'inbox', 'trail', 'list_tasks' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,11 @@
--- /mnt/storage/scratch/rocko-r73-out/mutants-base/orig 2026-10-11 18:09:25.821593099 -0500
+++ packages/harness/src/tools.mjs 2026-10-11 18:09:30.532803215 -0500
@@ -89,7 +89,7 @@
}
if (tracker) {
for (const [action, [name, description, properties, required]] of Object.entries(TASK_TOOLS)) {
- if (held.has(action)) add(name, action, action, description, obj(properties, required));
+ add(name, action, action, description, obj(properties, required));
}
}
add("raise_decision", "decision.raise", null, "Ask for a decision before a gated or cross-role action, or when you need a choice made. The answer arrives as a message.", obj({
@@ -0,0 +1,104 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (68.304242ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (36.220226ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (28.451192ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (17.620543ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (17.134783ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (17.523052ms)
✔ task action subjects and linked decision trail are complete and ordered (28.196094ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (11.654167ms)
✔ business isolation includes inherited object names and cross-business message references (17.773328ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (63.640909ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (30.76888ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (72.884982ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (38.656622ms)
✔ both arbiters require human resolution when their cross-role route is themselves (86.595811ms)
✔ systemVars flattens the validated config (3.418009ms)
✔ precedence: system, business, project, project role, agent (15.12961ms)
✔ limits narrow the definition and never widen it (32.983425ms)
✔ role.launch stays within-role only for the instance the launch block names (11.91897ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (2.882384ms)
✔ limits.authority narrows cross-role actions too (3.171349ms)
✔ classify (3.090892ms)
✔ the record carries what the broker and launcher need (2.902871ms)
✔ digest: key order doesn't matter, any value change does (14.329947ms)
✔ refusals (3.553461ms)
✔ the four shipped version 2 roles load (6.936567ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.580417ms)
✔ shipped authority follows the note's table (10.829435ms)
✔ version 1 files keep loading with no authority (1.892986ms)
✔ the conductor policy isn't a role (0.383968ms)
✔ a missing role file is exit 4, a symbolic link too (0.600117ms)
✔ version 2 refusals (1.966349ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (4.284811ms)
✔ credentials: Gitea scopes (1.537433ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.912443ms)
✔ credentials: services (1.044154ms)
✔ contract: a non-empty regular Markdown file beside the role file (1.05934ms)
✔ the PM gets launch, its task verbs and the reads (12.754906ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (4.823918ms)
✔ launch only when the business's launch block names the instance as launcher (3.337282ms)
✖ an action outside the instance's authority has no tool (6.565686ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (19.618993ms)
✔ task.create needs a recorded human request and a requirement id (57.310658ms)
✔ only labels named in the business file can be written (37.756552ms)
✔ task.schedule sets and clears a due date and relations (44.948474ms)
✔ assign and reassign move the role bots and record task.assigned (133.26048ms)
✔ task.update.assigned is for the assignee and records task.state (149.616551ms)
✔ a wrong expected digest records task.conflict and writes nothing (60.89642ms)
✔ a cross-role verb needs a resolved decision, used once (61.058916ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (43.717227ms)
✔ a lost answer is settled by a re-read and never retried (60.615688ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (50.786839ms)
✔ a task the sync bot cannot read refuses and records nothing (33.128318ms)
✔ verbs and polls for one business run one at a time (106.542625ms)
✔ a due date with milliseconds is written to the second (89.23896ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (52.735737ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (38.534275ms)
✔ a create whose final read fails succeeds and records task.created (48.772964ms)
✔ an edit between the last write and the final read shows as external on the next poll (64.248179ms)
✔ task.created is recorded when a later label write fails (23.685508ms)
ℹ tests 59
ℹ suites 0
ℹ pass 58
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1367.254328
✖ failing tests:
test at packages/harness/tests/tools.test.mjs:44:1
✖ an action outside the instance's authority has no tool (6.565686ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
+ 'task_create',
+ 'task_assign',
+ 'task_reassign',
+ 'task_schedule',
+ 'task_priority_change',
+ 'task_scope_change',
+ 'task_update_assigned',
+ 'task_close',
'raise_decision',
'list_agents',
'inbox',
'trail',
'list_tasks'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73-base/packages/harness/tests/tools.test.mjs:46: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: [ 'task_create', 'task_assign', 'task_reassign', 'task_schedule', 'task_priority_change', 'task_scope_change', 'task_update_assigned', 'task_close', 'raise_decision', 'list_agents', 'inbox', 'trail', 'list_tasks' ],
expected: [ 'raise_decision', 'list_agents', 'inbox', 'trail', 'list_tasks' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,6 @@
baseline: pass
role-keeps-reassign: killed (✖ a coder with a tracker gets its own task tools and no task_reassign;✖ classify;✖ shipped authority follows the note's table;✖ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role;)
broker-unlisted-cross: killed (✖ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role;)
resolve-cross-unnarrowed: killed (✖ an action outside the instance's authority has no tool;✖ limits.authority narrows cross-role actions too;)
classify-unlisted-cross: killed (✖ classify;✖ limits.authority narrows cross-role actions too;✖ limits.authority without role.launch leaves the launcher with no launch block;✖ limits narrow the definition and never widen it;✖ role.launch stays within-role only for the instance the launch block names;)
tools-all-task-verbs: killed (✖ a coder with a tracker gets its own task tools and no task_reassign;✖ an action outside the instance's authority has no tool;)
@@ -0,0 +1,69 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (22.59035ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (26.760387ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (25.941999ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (14.175334ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (18.000242ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (17.704479ms)
✔ task action subjects and linked decision trail are complete and ordered (27.462393ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (11.228427ms)
✔ business isolation includes inherited object names and cross-business message references (18.813263ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (22.467431ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (10.563551ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (15.243023ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (13.037666ms)
✔ both arbiters require human resolution when their cross-role route is themselves (24.729793ms)
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (19.098206ms)
✔ systemVars flattens the validated config (1.823722ms)
✔ precedence: system, business, project, project role, agent (7.082391ms)
✔ limits narrow the definition and never widen it (4.101089ms)
✔ role.launch stays within-role only for the instance the launch block names (7.044571ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (3.176026ms)
✔ limits.authority narrows cross-role actions too (3.958115ms)
✔ classify (1.566137ms)
✔ the record carries what the broker and launcher need (1.679601ms)
✔ digest: key order doesn't matter, any value change does (9.40251ms)
✔ refusals (3.314557ms)
✔ the four shipped version 2 roles load (4.562073ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.373138ms)
✔ shipped authority follows the note's table (0.880774ms)
✔ version 1 files keep loading with no authority (1.340018ms)
✔ the conductor policy isn't a role (0.294463ms)
✔ a missing role file is exit 4, a symbolic link too (0.573454ms)
✔ version 2 refusals (2.142424ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (2.806218ms)
✔ credentials: Gitea scopes (1.390039ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.74487ms)
✔ credentials: services (1.066435ms)
✔ contract: a non-empty regular Markdown file beside the role file (1.503718ms)
✔ the PM gets launch, its task verbs and the reads (7.458116ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.543347ms)
✔ a coder with a tracker gets its own task tools and no task_reassign (2.204646ms)
✔ launch only when the business's launch block names the instance as launcher (1.84137ms)
✔ an action outside the instance's authority has no tool (2.443015ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (11.834983ms)
✔ task.create needs a recorded human request and a requirement id (52.551332ms)
✔ only labels named in the business file can be written (36.191609ms)
✔ task.schedule sets and clears a due date and relations (42.137434ms)
✔ assign and reassign move the role bots and record task.assigned (61.839072ms)
✔ task.update.assigned is for the assignee and records task.state (64.945829ms)
✔ a wrong expected digest records task.conflict and writes nothing (26.65454ms)
✔ a cross-role verb needs a resolved decision, used once (33.944638ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (33.109479ms)
✔ a lost answer is settled by a re-read and never retried (34.373385ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (39.628552ms)
✔ a task the sync bot cannot read refuses and records nothing (18.56014ms)
✔ verbs and polls for one business run one at a time (99.689243ms)
✔ a due date with milliseconds is written to the second (72.921801ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (43.896182ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (18.194316ms)
✔ a create whose final read fails succeeds and records task.created (39.258208ms)
✔ an edit between the last write and the final read shows as external on the next poll (42.888728ms)
✔ task.created is recorded when a later label write fails (17.415777ms)
ℹ tests 61
ℹ suites 0
ℹ pass 61
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 906.528938
@@ -0,0 +1,11 @@
--- /mnt/storage/scratch/rocko-r73-out/mutants-candidate/orig 2026-10-11 18:07:23.086216318 -0500
+++ packages/bus/src/broker.mjs 2026-10-11 18:14:56.826892306 -0500
@@ -274,7 +274,7 @@
? 'within-role'
: a?.crossRole.includes(action)
? 'cross-role'
- : 'gated';
+ : 'cross-role';
}
#decision(s, id) {
const d = this.#store.get('SELECT * FROM decisions WHERE id=? AND business=?', id, s.business);
@@ -0,0 +1,94 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (28.422584ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (25.633236ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (21.236303ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (14.292941ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (16.245186ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (14.466042ms)
✔ task action subjects and linked decision trail are complete and ordered (24.311959ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (9.415662ms)
✔ business isolation includes inherited object names and cross-business message references (17.125506ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (20.297014ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (11.125627ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (17.296304ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (11.986569ms)
✔ both arbiters require human resolution when their cross-role route is themselves (19.617388ms)
✖ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (18.46808ms)
✔ systemVars flattens the validated config (3.618153ms)
✔ precedence: system, business, project, project role, agent (7.398492ms)
✔ limits narrow the definition and never widen it (3.541406ms)
✔ role.launch stays within-role only for the instance the launch block names (7.555402ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (3.395477ms)
✔ limits.authority narrows cross-role actions too (1.804949ms)
✔ classify (2.023344ms)
✔ the record carries what the broker and launcher need (1.416391ms)
✔ digest: key order doesn't matter, any value change does (7.582607ms)
✔ refusals (6.552895ms)
✔ the four shipped version 2 roles load (3.303484ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.217421ms)
✔ shipped authority follows the note's table (0.789951ms)
✔ version 1 files keep loading with no authority (1.087748ms)
✔ the conductor policy isn't a role (0.215847ms)
✔ a missing role file is exit 4, a symbolic link too (0.407959ms)
✔ version 2 refusals (1.339954ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (2.502025ms)
✔ credentials: Gitea scopes (1.02232ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.91448ms)
✔ credentials: services (0.677159ms)
✔ contract: a non-empty regular Markdown file beside the role file (0.758295ms)
✔ the PM gets launch, its task verbs and the reads (7.934889ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.113216ms)
✔ a coder with a tracker gets its own task tools and no task_reassign (1.793071ms)
✔ launch only when the business's launch block names the instance as launcher (1.614854ms)
✔ an action outside the instance's authority has no tool (4.100632ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (10.303855ms)
✔ task.create needs a recorded human request and a requirement id (43.05711ms)
✔ only labels named in the business file can be written (27.611979ms)
✔ task.schedule sets and clears a due date and relations (39.061519ms)
✔ assign and reassign move the role bots and record task.assigned (62.22098ms)
✔ task.update.assigned is for the assignee and records task.state (55.970065ms)
✔ a wrong expected digest records task.conflict and writes nothing (25.10594ms)
✔ a cross-role verb needs a resolved decision, used once (27.276288ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (27.716914ms)
✔ a lost answer is settled by a re-read and never retried (29.052452ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (39.47631ms)
✔ a task the sync bot cannot read refuses and records nothing (20.196553ms)
✔ verbs and polls for one business run one at a time (92.77395ms)
✔ a due date with milliseconds is written to the second (67.659021ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (39.62171ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (16.201161ms)
✔ a create whose final read fails succeeds and records task.created (35.582005ms)
✔ an edit between the last write and the final read shows as external on the next poll (44.800687ms)
✔ task.created is recorded when a later label write fails (15.946503ms)
ℹ tests 61
ℹ suites 0
ℹ pass 60
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 829.410177
✖ failing tests:
test at packages/bus/tests/broker.test.mjs:361:1
✖ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (18.46808ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'cross-role'
- 'gated'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/bus/tests/broker.test.mjs:374: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: 'cross-role',
expected: 'gated',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,9 @@
--- /mnt/storage/scratch/rocko-r73-out/mutants-candidate/orig 2026-10-11 18:07:23.087807382 -0500
+++ packages/business/src/resolve.mjs 2026-10-11 18:14:58.604372993 -0500
@@ -100,5 +100,5 @@
if (!ACTIONS.includes(action)) refuse(`unknown action: ${JSON.stringify(action)}`);
if (resolved.limits.authority.withinRole.includes(action)) return "within";
if (resolved.limits.authority.crossRole.includes(action)) return "cross";
- return "gated";
+ return "cross";
}
@@ -0,0 +1,176 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (32.115093ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (20.806345ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (20.504225ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (15.031318ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (13.795536ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (12.743478ms)
✔ task action subjects and linked decision trail are complete and ordered (20.909183ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (11.281359ms)
✔ business isolation includes inherited object names and cross-business message references (14.550486ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (18.52182ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (11.841502ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (16.416647ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (10.122398ms)
✔ both arbiters require human resolution when their cross-role route is themselves (18.177921ms)
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (16.866523ms)
✔ systemVars flattens the validated config (2.898361ms)
✔ precedence: system, business, project, project role, agent (7.459935ms)
✖ limits narrow the definition and never widen it (4.478519ms)
✖ role.launch stays within-role only for the instance the launch block names (5.021168ms)
✖ limits.authority without role.launch leaves the launcher with no launch block (1.627639ms)
✖ limits.authority narrows cross-role actions too (1.276774ms)
✖ classify (1.320238ms)
✔ the record carries what the broker and launcher need (1.382509ms)
✔ digest: key order doesn't matter, any value change does (7.774975ms)
✔ refusals (2.214875ms)
✔ the four shipped version 2 roles load (3.34149ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.017705ms)
✔ shipped authority follows the note's table (0.534057ms)
✔ version 1 files keep loading with no authority (0.917276ms)
✔ the conductor policy isn't a role (0.211232ms)
✔ a missing role file is exit 4, a symbolic link too (0.388821ms)
✔ version 2 refusals (1.302921ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (2.052877ms)
✔ credentials: Gitea scopes (0.840766ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.03215ms)
✔ credentials: services (0.560366ms)
✔ contract: a non-empty regular Markdown file beside the role file (0.659056ms)
✔ the PM gets launch, its task verbs and the reads (7.878729ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.195081ms)
✔ a coder with a tracker gets its own task tools and no task_reassign (1.922068ms)
✔ launch only when the business's launch block names the instance as launcher (1.571719ms)
✔ an action outside the instance's authority has no tool (2.458934ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (10.132477ms)
✔ task.create needs a recorded human request and a requirement id (42.270988ms)
✔ only labels named in the business file can be written (28.695884ms)
✔ task.schedule sets and clears a due date and relations (35.181345ms)
✔ assign and reassign move the role bots and record task.assigned (55.663835ms)
✔ task.update.assigned is for the assignee and records task.state (55.137406ms)
✔ a wrong expected digest records task.conflict and writes nothing (21.171762ms)
✔ a cross-role verb needs a resolved decision, used once (27.361206ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (28.545856ms)
✔ a lost answer is settled by a re-read and never retried (30.01709ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (37.911184ms)
✔ a task the sync bot cannot read refuses and records nothing (18.605202ms)
✔ verbs and polls for one business run one at a time (93.947085ms)
✔ a due date with milliseconds is written to the second (72.13725ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (45.828384ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (24.061498ms)
✔ a create whose final read fails succeeds and records task.created (47.741873ms)
✔ an edit between the last write and the final read shows as external on the next poll (43.688712ms)
✔ task.created is recorded when a later label write fails (19.256173ms)
ℹ tests 61
ℹ suites 0
ℹ pass 56
ℹ fail 5
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 843.294318
✖ failing tests:
test at packages/business/tests/resolve.test.mjs:69:1
✖ limits narrow the definition and never widen it (4.478519ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'cross' !== 'gated'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/business/tests/resolve.test.mjs:87: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: 'cross',
expected: 'gated',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/business/tests/resolve.test.mjs:91:1
✖ role.launch stays within-role only for the instance the launch block names (5.021168ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'cross' !== 'gated'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/business/tests/resolve.test.mjs:98: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: 'cross',
expected: 'gated',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/business/tests/resolve.test.mjs:112:1
✖ limits.authority without role.launch leaves the launcher with no launch block (1.627639ms)
AssertionError [ERR_ASSERTION]: agent
'cross' !== 'gated'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/business/tests/resolve.test.mjs:119:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 'cross',
expected: 'gated',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/business/tests/resolve.test.mjs:124:1
✖ limits.authority narrows cross-role actions too (1.276774ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'cross' !== 'gated'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/business/tests/resolve.test.mjs:132: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: 'cross',
expected: 'gated',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/business/tests/resolve.test.mjs:136:1
✖ classify (1.320238ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'cross' !== 'gated'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/business/tests/resolve.test.mjs:141: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: 'cross',
expected: 'gated',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,11 @@
--- /mnt/storage/scratch/rocko-r73-out/mutants-candidate/orig 2026-10-11 18:07:23.087807382 -0500
+++ packages/business/src/resolve.mjs 2026-10-11 18:14:57.707967161 -0500
@@ -65,7 +65,7 @@
if (vars["limits.network"]) network = narrowerNetwork(network, vars["limits.network"]);
if (vars["limits.authority"]) {
withinRole = withinRole.filter((a) => vars["limits.authority"].includes(a));
- crossRole = crossRole.filter((a) => vars["limits.authority"].includes(a));
+ crossRole = [...crossRole];
}
// role.launch needs the business file's launch block naming this
// instance (addendum A section 8), and the verb must survive
@@ -0,0 +1,129 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (33.103276ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (28.078672ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (23.400199ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (16.425704ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (12.498957ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (12.625172ms)
✔ task action subjects and linked decision trail are complete and ordered (23.3379ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (8.693012ms)
✔ business isolation includes inherited object names and cross-business message references (19.263813ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (21.458088ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (9.875642ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (17.784604ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (11.20225ms)
✔ both arbiters require human resolution when their cross-role route is themselves (18.979522ms)
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (16.746697ms)
✔ systemVars flattens the validated config (3.032988ms)
✔ precedence: system, business, project, project role, agent (7.573478ms)
✔ limits narrow the definition and never widen it (3.723962ms)
✔ role.launch stays within-role only for the instance the launch block names (7.177622ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (3.083517ms)
✖ limits.authority narrows cross-role actions too (2.477071ms)
✔ classify (1.655321ms)
✔ the record carries what the broker and launcher need (1.756283ms)
✔ digest: key order doesn't matter, any value change does (8.241698ms)
✔ refusals (3.408223ms)
✔ the four shipped version 2 roles load (4.389377ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.557139ms)
✔ shipped authority follows the note's table (0.924885ms)
✔ version 1 files keep loading with no authority (1.425297ms)
✔ the conductor policy isn't a role (0.314017ms)
✔ a missing role file is exit 4, a symbolic link too (0.622313ms)
✔ version 2 refusals (2.290655ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (3.899517ms)
✔ credentials: Gitea scopes (1.46446ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.844948ms)
✔ credentials: services (0.874648ms)
✔ contract: a non-empty regular Markdown file beside the role file (0.942239ms)
✔ the PM gets launch, its task verbs and the reads (10.058394ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (3.002875ms)
✔ a coder with a tracker gets its own task tools and no task_reassign (2.268428ms)
✔ launch only when the business's launch block names the instance as launcher (1.651138ms)
✖ an action outside the instance's authority has no tool (4.595551ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (10.307219ms)
✔ task.create needs a recorded human request and a requirement id (50.783873ms)
✔ only labels named in the business file can be written (27.907976ms)
✔ task.schedule sets and clears a due date and relations (33.79153ms)
✔ assign and reassign move the role bots and record task.assigned (61.7935ms)
✔ task.update.assigned is for the assignee and records task.state (54.786754ms)
✔ a wrong expected digest records task.conflict and writes nothing (21.537052ms)
✔ a cross-role verb needs a resolved decision, used once (25.702853ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (25.182622ms)
✔ a lost answer is settled by a re-read and never retried (28.600271ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (39.54527ms)
✔ a task the sync bot cannot read refuses and records nothing (16.765549ms)
✔ verbs and polls for one business run one at a time (89.463681ms)
✔ a due date with milliseconds is written to the second (66.833039ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (64.467321ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (23.033961ms)
✔ a create whose final read fails succeeds and records task.created (36.136121ms)
✔ an edit between the last write and the final read shows as external on the next poll (39.918485ms)
✔ task.created is recorded when a later label write fails (13.988664ms)
ℹ tests 61
ℹ suites 0
ℹ pass 59
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 852.228915
✖ failing tests:
test at packages/business/tests/resolve.test.mjs:124:1
✖ limits.authority narrows cross-role actions too (2.477071ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
{
crossRole: [
'task.priority.change',
+ 'task.scope.change'
],
withinRole: [
'task.create',
'task.close'
]
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/business/tests/resolve.test.mjs:130: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: { withinRole: [ 'task.create', 'task.close' ], crossRole: [ 'task.priority.change', 'task.scope.change' ] },
expected: { withinRole: [ 'task.create', 'task.close' ], crossRole: [ 'task.priority.change' ] },
operator: 'deepStrictEqual',
diff: 'simple'
}
test at packages/harness/tests/tools.test.mjs:52:1
✖ an action outside the instance's authority has no tool (4.595551ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
+ 'task_scope_change',
'raise_decision',
'list_agents',
'inbox',
'trail',
'list_tasks'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/harness/tests/tools.test.mjs:54: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: [ 'task_scope_change', 'raise_decision', 'list_agents', 'inbox', 'trail', 'list_tasks' ],
expected: [ 'raise_decision', 'list_agents', 'inbox', 'trail', 'list_tasks' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,11 @@
--- /mnt/storage/scratch/rocko-r73-out/mutants-candidate/orig 2026-10-11 18:07:39.870939784 -0500
+++ roles/coder.json 2026-10-11 18:14:55.818223939 -0500
@@ -7,7 +7,7 @@
"network": "api-only",
"authority": {
"withinRole": ["task.update.assigned", "git.push.working", "review.request", "message.send"],
- "crossRole": ["task.scope.change"]
+ "crossRole": ["task.reassign", "task.scope.change"]
},
"credentials": [
{
@@ -0,0 +1,169 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (25.610404ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (26.850363ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (23.84385ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (12.839692ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (14.696717ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (13.166518ms)
✔ task action subjects and linked decision trail are complete and ordered (19.883322ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (10.292636ms)
✔ business isolation includes inherited object names and cross-business message references (17.143026ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (20.432048ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (9.413307ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (17.112756ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (11.689255ms)
✔ both arbiters require human resolution when their cross-role route is themselves (22.916955ms)
✖ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (25.762402ms)
✔ systemVars flattens the validated config (3.032631ms)
✔ precedence: system, business, project, project role, agent (8.016805ms)
✔ limits narrow the definition and never widen it (3.808311ms)
✔ role.launch stays within-role only for the instance the launch block names (9.313641ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (4.009638ms)
✔ limits.authority narrows cross-role actions too (1.829767ms)
✖ classify (2.894526ms)
✔ the record carries what the broker and launcher need (1.969378ms)
✔ digest: key order doesn't matter, any value change does (9.350735ms)
✔ refusals (2.563428ms)
✔ the four shipped version 2 roles load (4.282478ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.481512ms)
✖ shipped authority follows the note's table (1.731521ms)
✔ version 1 files keep loading with no authority (1.117387ms)
✔ the conductor policy isn't a role (0.251829ms)
✔ a missing role file is exit 4, a symbolic link too (0.421436ms)
✔ version 2 refusals (1.742865ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (2.625104ms)
✔ credentials: Gitea scopes (2.325317ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (2.201409ms)
✔ credentials: services (1.168802ms)
✔ contract: a non-empty regular Markdown file beside the role file (1.325241ms)
✔ the PM gets launch, its task verbs and the reads (8.84067ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.555972ms)
✖ a coder with a tracker gets its own task tools and no task_reassign (3.580158ms)
✔ launch only when the business's launch block names the instance as launcher (1.736644ms)
✔ an action outside the instance's authority has no tool (3.187475ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (10.303208ms)
✔ task.create needs a recorded human request and a requirement id (47.944003ms)
✔ only labels named in the business file can be written (26.77445ms)
✔ task.schedule sets and clears a due date and relations (33.848522ms)
✔ assign and reassign move the role bots and record task.assigned (61.702775ms)
✔ task.update.assigned is for the assignee and records task.state (68.22306ms)
✔ a wrong expected digest records task.conflict and writes nothing (26.375322ms)
✔ a cross-role verb needs a resolved decision, used once (45.781054ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (58.667462ms)
✔ a lost answer is settled by a re-read and never retried (47.540574ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (48.995082ms)
✔ a task the sync bot cannot read refuses and records nothing (21.496242ms)
✔ verbs and polls for one business run one at a time (115.361345ms)
✔ a due date with milliseconds is written to the second (77.442629ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (44.709765ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (20.37596ms)
✔ a create whose final read fails succeeds and records task.created (36.039383ms)
✔ an edit between the last write and the final read shows as external on the next poll (40.908102ms)
✔ task.created is recorded when a later label write fails (16.705741ms)
ℹ tests 61
ℹ suites 0
ℹ pass 57
ℹ fail 4
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 962.043519
✖ failing tests:
test at packages/bus/tests/broker.test.mjs:361:1
✖ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (25.762402ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'cross-role'
- 'gated'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/bus/tests/broker.test.mjs:374: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: 'cross-role',
expected: 'gated',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/business/tests/resolve.test.mjs:136:1
✖ classify (2.894526ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'cross' !== 'gated'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/business/tests/resolve.test.mjs:141: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: 'cross',
expected: 'gated',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/business/tests/role.test.mjs:61:1
✖ shipped authority follows the note's table (1.731521ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
+ 'task.reassign',
'task.scope.change'
]
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/business/tests/role.test.mjs:65: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: [ 'task.reassign', 'task.scope.change' ],
expected: [ 'task.scope.change' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
test at packages/harness/tests/tools.test.mjs:38:1
✖ a coder with a tracker gets its own task tools and no task_reassign (3.580158ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
'send_message',
+ 'task_reassign',
'task_scope_change',
'task_update_assigned',
'raise_decision',
'list_agents',
'inbox',
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/harness/tests/tools.test.mjs:40: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: [ 'send_message', 'task_reassign', 'task_scope_change', 'task_update_assigned', 'raise_decision', 'list_agents', 'inbox', 'trail', 'list_tasks' ],
expected: [ 'send_message', 'task_scope_change', 'task_update_assigned', 'raise_decision', 'list_agents', 'inbox', 'trail', 'list_tasks' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,11 @@
--- /mnt/storage/scratch/rocko-r73-out/mutants-candidate/orig 2026-10-11 18:07:23.096112289 -0500
+++ packages/harness/src/tools.mjs 2026-10-11 18:14:59.504647794 -0500
@@ -89,7 +89,7 @@
}
if (tracker) {
for (const [action, [name, description, properties, required]] of Object.entries(TASK_TOOLS)) {
- if (held.has(action)) add(name, action, action, description, obj(properties, required));
+ add(name, action, action, description, obj(properties, required));
}
}
add("raise_decision", "decision.raise", null, "Ask for a decision before a gated or cross-role action, or when you need a choice made. The answer arrives as a message.", obj({
@@ -0,0 +1,142 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (36.639909ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (33.673177ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (32.106496ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (18.481664ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (16.132294ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (16.873703ms)
✔ task action subjects and linked decision trail are complete and ordered (24.302336ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (10.520395ms)
✔ business isolation includes inherited object names and cross-business message references (17.798456ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (25.213949ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (13.067758ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (20.466512ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (12.744716ms)
✔ both arbiters require human resolution when their cross-role route is themselves (19.443375ms)
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (17.797876ms)
✔ systemVars flattens the validated config (3.526166ms)
✔ precedence: system, business, project, project role, agent (8.517006ms)
✔ limits narrow the definition and never widen it (4.025856ms)
✔ role.launch stays within-role only for the instance the launch block names (7.700117ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (5.183853ms)
✔ limits.authority narrows cross-role actions too (1.75868ms)
✔ classify (2.147984ms)
✔ the record carries what the broker and launcher need (1.566134ms)
✔ digest: key order doesn't matter, any value change does (10.446916ms)
✔ refusals (3.104062ms)
✔ the four shipped version 2 roles load (5.416941ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.42006ms)
✔ shipped authority follows the note's table (0.78467ms)
✔ version 1 files keep loading with no authority (1.197489ms)
✔ the conductor policy isn't a role (0.278589ms)
✔ a missing role file is exit 4, a symbolic link too (0.489048ms)
✔ version 2 refusals (1.578833ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (2.399299ms)
✔ credentials: Gitea scopes (1.394806ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.771779ms)
✔ credentials: services (1.393594ms)
✔ contract: a non-empty regular Markdown file beside the role file (1.18444ms)
✔ the PM gets launch, its task verbs and the reads (9.197271ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (3.379526ms)
✖ a coder with a tracker gets its own task tools and no task_reassign (5.596139ms)
✔ launch only when the business's launch block names the instance as launcher (3.973547ms)
✖ an action outside the instance's authority has no tool (3.762374ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (15.066181ms)
✔ task.create needs a recorded human request and a requirement id (62.733557ms)
✔ only labels named in the business file can be written (34.096604ms)
✔ task.schedule sets and clears a due date and relations (44.787245ms)
✔ assign and reassign move the role bots and record task.assigned (66.206408ms)
✔ task.update.assigned is for the assignee and records task.state (57.83302ms)
✔ a wrong expected digest records task.conflict and writes nothing (26.848484ms)
✔ a cross-role verb needs a resolved decision, used once (26.339943ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (30.531726ms)
✔ a lost answer is settled by a re-read and never retried (28.266487ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (39.805487ms)
✔ a task the sync bot cannot read refuses and records nothing (21.989204ms)
✔ verbs and polls for one business run one at a time (99.205086ms)
✔ a due date with milliseconds is written to the second (75.658928ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (39.315806ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (19.951694ms)
✔ a create whose final read fails succeeds and records task.created (42.258201ms)
✔ an edit between the last write and the final read shows as external on the next poll (97.83506ms)
✔ task.created is recorded when a later label write fails (26.007172ms)
ℹ tests 61
ℹ suites 0
ℹ pass 59
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 987.057475
✖ failing tests:
test at packages/harness/tests/tools.test.mjs:38:1
✖ a coder with a tracker gets its own task tools and no task_reassign (5.596139ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
'send_message',
+ 'task_create',
+ 'task_assign',
+ 'task_reassign',
+ 'task_schedule',
+ 'task_priority_change',
'task_scope_change',
'task_update_assigned',
+ 'task_close',
'raise_decision',
'list_agents',
'inbox',
'trail',
'list_tasks'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/harness/tests/tools.test.mjs:40: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: [ 'send_message', 'task_create', 'task_assign', 'task_reassign', 'task_schedule', 'task_priority_change', 'task_scope_change', 'task_update_assigned', 'task_close', 'raise_decision', 'list_agents', 'inbox', 'trail', 'list_tasks' ],
expected: [ 'send_message', 'task_scope_change', 'task_update_assigned', 'raise_decision', 'list_agents', 'inbox', 'trail', 'list_tasks' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
test at packages/harness/tests/tools.test.mjs:52:1
✖ an action outside the instance's authority has no tool (3.762374ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
+ 'task_create',
+ 'task_assign',
+ 'task_reassign',
+ 'task_schedule',
+ 'task_priority_change',
+ 'task_scope_change',
+ 'task_update_assigned',
+ 'task_close',
'raise_decision',
'list_agents',
'inbox',
'trail',
'list_tasks'
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r73/packages/harness/tests/tools.test.mjs:54: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: [ 'task_create', 'task_assign', 'task_reassign', 'task_schedule', 'task_priority_change', 'task_scope_change', 'task_update_assigned', 'task_close', 'raise_decision', 'list_agents', 'inbox', 'trail', 'list_tasks' ],
expected: [ 'raise_decision', 'list_agents', 'inbox', 'trail', 'list_tasks' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,2 @@
ℹ pass 1
ℹ fail 0
+4
View File
@@ -0,0 +1,4 @@
900b080b9d61e9aa00fd7e6aaf04d84d roles/coder.json
3773d12359d85a4ec93bcdd2c2b96101 packages/bus/src/broker.mjs
0b70de33a0391963899fa3d98613bf40 packages/business/src/resolve.mjs
e26512bb2646ce69ef9bd127ab6c6b96 packages/harness/src/tools.mjs
@@ -0,0 +1,3 @@
task.reassign class cross-role route_to pm
task.scope.change class cross-role route_to pm
coder tools with tracker: send_message,task_reassign,task_scope_change,task_update_assigned,raise_decision,list_agents,inbox,trail,list_tasks
@@ -0,0 +1,3 @@
task.reassign class gated route_to human
task.scope.change class cross-role route_to pm
coder tools with tracker: send_message,task_scope_change,task_update_assigned,raise_decision,list_agents,inbox,trail,list_tasks
+27
View File
@@ -0,0 +1,27 @@
// Probe: what the broker and harness do today with the shipped coder role.
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const tree = process.argv[2];
const { Store } = await import(join(tree, 'packages/bus/src/store.mjs'));
const { Broker } = await import(join(tree, 'packages/bus/src/broker.mjs'));
const { busBusiness } = await import(join(tree, 'packages/bus/src/business.mjs'));
const { validateBusinessDocument, resolveInstance } = await import(join(tree, 'packages/business/src/index.mjs'));
const { businessDoc, REPO_ROLES, systemFor } = await import(join(tree, 'packages/business/tests/helpers.mjs'));
const { typedTools } = await import(join(tree, 'packages/harness/src/tools.mjs'));
const root = mkdtempSync(join(tmpdir(), 'r73-probe-'));
try {
const business = validateBusinessDocument(businessDoc(root, 'demo'), join(root, 'businesses', 'demo.json'), { rolesDir: REPO_ROLES });
const resolved = Object.fromEntries(Object.keys(business.roles).map((i) => [i, resolveInstance({ system: systemFor(root), business, instance: i })]));
const store = new Store(root);
const b = new Broker({ store, businesses: { demo: busBusiness(business, resolved) } });
const c = b.bindLaunch({ business: 'demo', role: 'coder', run: 'coder-run', harness: 'pi', address: 'coder-run' });
b.request(c, { verb: 'role.claim', args: {} });
const options = [{ key: 'yes', text: 'Allow' }, { key: 'no', text: 'Decline' }];
for (const action of ['task.reassign', 'task.scope.change']) {
const d = b.request(c, { verb: 'decision.raise', args: { action, question: 'Allow?', options, recommendation: 'no', blocking: false } });
console.log(action, 'class', d.class, 'route_to', d.route_to);
}
console.log('coder tools with tracker:', typedTools({ resolved: resolved.coder, tracker: true }).map((t) => t.name).join(','));
store.close();
} finally { rmSync(root, { recursive: true, force: true }); }
@@ -0,0 +1,53 @@
a101b9dc7a08d79018dd8c6ac77dcf451901b0b8910de1a792367cf1f4a35b1a ./base.txt
57e963e58c1f17fb0cb8cbf008ba5e305fc4555d4f0696723c85edc7871f8256 ./BUILD.md
63db27a4bd80eec09da09c1a3b7de7977d096447de313b2f42de5c60efbeeb60 ./build.patch
bf4e5ed646301d06617004770aaaf9599e743f1e00d226f8a9e0c2450f12710d ./candidate-manifest.sha256
516fbe81e33a9d44351758deca7d18d580a7a1d640510ec2941906ed277bdadd ./files.txt
e3300acd1f85f7c7c495dc2228ec904c0af992f9ea09693adcf3e5b1121d5f83 ./mutants.sh
f8b54deb8f32f8c420ed5a503d4623e89bee762ff2af5ce74185b1fc9809024b ./out/gate/business.log
b364089f6df19de5086c1c15258696fd9a719cfcbb672ec5d504a065fc01178e ./out/gate/bus.log
bbbf01cda98f6710f35e48a0ed93277b98b010bfa27d7bad1c13d46d40ea4bd4 ./out/gate/cli.log
cb7eb33230c15549e0d6cccf84125db76c4f08a420da585560d4d2cea3fc3935 ./out/gate/end.txt
0eb63fc07e1e77a362a26513b46be8fd77547c033082b4ce969c4819cd4b9898 ./out/gate/harness.log
84dfcd7429af68d3c9e1cff2044dc3c8a33524931333f6b6c7842107c1809058 ./out/gate/seat.log
bb11ccdba6113bd6441edffadbfc31889ad31a8ae5a1b7730efd76479f70a0f5 ./out/gate.sh
f594c4c232b18e1c5e943a5bbe0776c34768a850923ee6d796f1bcc6cd81ca3b ./out/gate/start.txt
9c18698db3974473d3f5c4adf65d959050c9dcc356daff8a307274fb7274be53 ./out/gate/summary.txt
afefb840318fad067fd0080e65559080c09e2e58d3bd6bdd95ce1133cbe4954d ./out/gate/tasks.log
f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 ./out/gate/test-auth.log
e1206366f07f3d9029530dcdcb55c0282d3f052adc60fc7fd51642d5d4ec3600 ./out/gate/test-conductor.log
52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 ./out/gate/test-config.log
7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe ./out/gate/test-discord.log
1ae08907ecdf1fa939b6f051b22d28462086a9d9f0f76c2d35b9ba2b21132e27 ./out/gate/test-extension-package.log
83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f ./out/gate/test-foundation.log
b413b13dda4ccebcf63dd3d5958e0d6cdfcacc124a8f478b712bccc8c69ffd52 ./out/gate/test-queue.log
fa832794ee35025fb4559d58d01a270f6bff2eb65e863e9075b797f47fa68545 ./out/gate/test-release.log
ccea37f4ffde1f160dcd1a1864988083a645d974e892065c956b338d0f8c853a ./out/gate/test-task.log
025ce7565cacda5a0e84d23a87ee040eeca76605178a9da0344f1de85ed51284 ./out/gate/webui.log
405f17ea5490a22cd8175ad3d70383a0c7df6a9e9f4b8a0a773117bf97a1a667 ./out/mutants-base/baseline.log
c473c06ab442b60be7352c16b42de590c32d981f666b32c8bca8a7ca57fc37eb ./out/mutants-base/broker-unlisted-cross.diff
92c8cf650edd7ec6d7fdbe456b28e6d2cb1c99b5e9bf772a4468db91d2e2e5ff ./out/mutants-base/broker-unlisted-cross.log
98b872bc68686436afa6024a4a86466dfaefa0eebd24eef583248dab2fdd52a7 ./out/mutants-base/classify-unlisted-cross.diff
5b48e8dc706edc1df9a93471aca575efc4cadca283c5acfe0dc3c6d332d74b0c ./out/mutants-base/classify-unlisted-cross.log
149190c6f27c506716dbb90df1edd4872ad44c58b78cc77f4a0cf40e9d5e2e8b ./out/mutants-base/resolve-cross-unnarrowed.diff
5edaee8eba9467a5f9c78fcf55c13dff593412e20d44f2a85638035775335adb ./out/mutants-base/resolve-cross-unnarrowed.log
4fbcfbcd1a3b2307e1e3fc0ff4bcf1ffa3e90e6c600257a99c93aeeb9719b722 ./out/mutants-base/tools-all-task-verbs.diff
6c92f1d4b6e77ac5a2cb9c5a744a0c49d068b6fe0367716fd2529e553c70a8c3 ./out/mutants-base/tools-all-task-verbs.log
4ecd1ba8a8515846f15fe3af3ade57b5e04abb758f05d50a52e3c54f25c971f1 ./out/mutants-base.txt
a410b4ab2f7718bf7dc424f4fa3394ab9f59f70e396acd4dbd54a5933d3b2e53 ./out/mutants-candidate/baseline.log
cc6be3793b2e5e77dde9adaeda87d73153c2e5286a6582b26f8ca54699a7a1a9 ./out/mutants-candidate/broker-unlisted-cross.diff
f8d26304746eeeec72dd4b7f0e4bfa34a9b393156dd89ae0cc4df338bfe27774 ./out/mutants-candidate/broker-unlisted-cross.log
61e5bf405c309604799f1bfa3a87c04c5900673a55891c80a8859a8357985212 ./out/mutants-candidate/classify-unlisted-cross.diff
500f3837c9bea02e2228a8ade0f143649225ff6854d3d9468c9b4dddb50ffd92 ./out/mutants-candidate/classify-unlisted-cross.log
2bb9f2a49ec99852ce2c7ed19e6f01fc061c0f1a1a0aaa9e4a8f7469c41efe29 ./out/mutants-candidate/resolve-cross-unnarrowed.diff
9a7aadcaa01dc7b92d4180f4cd6a9ae266cfbcf4f7b49c4854b2dc13f06e0986 ./out/mutants-candidate/resolve-cross-unnarrowed.log
ca8f11dedc9de9bda0ea127548680b9c8cbdd0d8a9bf9352b0a42ff37f686f0c ./out/mutants-candidate/role-keeps-reassign.diff
96d653c7368821d451fae7cf57ea2bf005b4ab542e1a3d1c27294fe832b58c2f ./out/mutants-candidate/role-keeps-reassign.log
c3d83cbbb17b3bd788089610b3ee440f2c7e8124a4a8032e4f475e48d030ca55 ./out/mutants-candidate/tools-all-task-verbs.diff
75a35b18ea42f629fb45e620703c6647eff0e3efaf6968739e5d66ef718e40c7 ./out/mutants-candidate/tools-all-task-verbs.log
60d077be2cfad3dc9c365a0701b535b48cbaea5a87def47832992aa1f4138481 ./out/mutants-candidate.txt
719ccf9999d00adaaab22be5247a231e84d1e3077f9b8687c09caf4ab7bf63aa ./out/old-narrow-test-under-mutant.txt
82479aa50e513a3c764d3364298ddb1a3d148c38e32942de3f0f91c7c8df874b ./out/pre.md5
42475e9e7238f9daa54b54c1d37512ca172116918c48150ec6abc86047d7aa62 ./out/probe-base.txt
ae7e5be038f5b5db667bb7f4d08a00e6b1d5827c8024857ac642a8b1f8128452 ./out/probe-candidate.txt
3d44b91cd0e2b40e935812b35c41ef40df5e267e517f51af217ab38feb8507e1 ./out/probe.mjs