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:
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
324c064b2a9290ff6c17449c3fa0725aa7a09494
|
||||
@@ -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
|
||||
@@ -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
|
||||
Executable
+56
@@ -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"
|
||||
Executable
+16
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user