agents: Researcher row 120 candidate and verification packet
Broker send limits for the assistant (REQ-ASSIST-2, #1614): optional role
messaging lists, broker refusals logged both ways. Gate and 19 mutants green
on 85540a74e; patch applies to 9f1c14e57.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
9f1c14e57f
commit
07a967aec2
79 files changed
+3712
No files matched your search
@@ -0,0 +1,146 @@
|
||||
# Row 120 (#1614): the broker limits who the assistant sends to and hears from
|
||||
|
||||
Researcher, 2026-10-15. Brief: `docs/plans/2026-10-14_prd-1.2-build.md`, "The broker limits who the assistant
|
||||
sends to and hears from". Requirement: REQ-ASSIST-2 (`docs/prd/mosaic-stack.md:170`). Reviewer: Filbert. Gate
|
||||
owner: Sage. Gate text: "Filbert approves on #1614 naming the candidate manifest; Sage's full gate rerun at the
|
||||
landing target; both directions refused and logged, a role without the field unchanged".
|
||||
|
||||
Base: 85540a74e. `row120.patch` applies to it by `git apply --cached --check`, and to the canonical checkout's
|
||||
HEAD at packet time (9f1c14e57, `git apply --check` passes). No commit between 85540a74e and 9f1c14e57 touches
|
||||
a candidate path.
|
||||
|
||||
Row 122 also edits `packages/bus/src/broker.mjs` (constructor `organization` argument, a limits block before
|
||||
`#store.transaction`). It lands first (sage-0270). This row's constructor check sits after the existing
|
||||
assignments inside the per-business loop, and its `message.send` edit stays inside that case. After 122 lands I
|
||||
rebase, rerun the full gate and report; expect at most a textual neighbourhood conflict in the constructor.
|
||||
|
||||
## What changes
|
||||
|
||||
A role file may carry an optional `messaging` object: `sendTo` and `receiveFrom`, each a list of **role
|
||||
definition names** plus `human` for the user. The broker enforces whatever the role file says and hard-codes no
|
||||
role name. Only `roles/assistant.json` carries the field today:
|
||||
|
||||
```json
|
||||
"messaging": { "sendTo": ["human", "chief-of-staff"], "receiveFrom": ["human", "chief-of-staff"] }
|
||||
```
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `packages/business/src/role.mjs` | `V2_KEYS` gains `messaging` (optional; `V2_REQUIRED` keeps the presence check). New `checkMessaging`: object, only `sendTo` and `receiveFrom`, each a distinct list of ids, `tester` refused (never a role). The loaded role gains `messaging` (`null` when absent; v1 files stay `null` and reject the key). |
|
||||
| `packages/bus/src/broker.mjs` | Constructor refuses a malformed `definitions[*].messaging` with `invalid-messaging`. New `#checkMessaging(s, to)`, called in `message.send` after the `unknown-role` check. Sender side first (`message-recipient-refused`), then target side (`message-sender-refused`). |
|
||||
| `roles/assistant.json` | adds the `messaging` object above. |
|
||||
| `packages/business/tests/role.test.mjs` | v1 role has `messaging === null`; new test for the field's shape, the refusals, and `tester`. |
|
||||
| `packages/business/tests/assistant.test.mjs` | `APPROVED` gains the frozen lists; new test that the assistant is limited and no other shipped role is. |
|
||||
| `packages/bus/tests/messaging.test.mjs` (new) | 9 broker tests (below). |
|
||||
|
||||
### Why the broker reads `business.definitions`
|
||||
|
||||
Broker "roles" are business instance names (`chief`, `assistant`); each has `roles[x].definition`, the role
|
||||
definition name. A role file can't know a business's instance names, so the lists name definitions and the
|
||||
broker maps an instance to its definition. The business record handed to the broker is a structured clone of the
|
||||
loaded business and already carries `definitions`, so `busBusiness`, `resolve.mjs`, the digests and the
|
||||
launcher are untouched. No re-pin and no digest change come from this row. A business record with no
|
||||
`definitions` is unlimited; test 8 pins that.
|
||||
|
||||
Alternative rejected: have `busBusiness` copy the field onto `roles[x]`. It works, but `business.mjs` is outside
|
||||
the row's path list.
|
||||
|
||||
### Rules, as tested
|
||||
|
||||
- Sender with a `sendTo` list: the recipient's definition must be listed, `human` for the user. The tester is
|
||||
not a definition, so a limited role can't send to `tester` (a reply to the tester is blocked the same way).
|
||||
- Recipient with a `receiveFrom` list: the sender's definition must be listed; `human` for the user. The tester
|
||||
is never listed, so the tester's send to the assistant is refused. This is the tester default in the packet.
|
||||
- The sender check runs before the target check, with separate codes.
|
||||
- The human and the tester are checked on the target side only.
|
||||
- A role with no `messaging` is unchanged on both sides.
|
||||
- A refusal throws inside the request transaction: no message is stored, no `action.allowed` is consumed, and
|
||||
the catch writes `action.refused {code}` with the actor role and no request content.
|
||||
|
||||
### Filbert carry-in
|
||||
|
||||
"The broker must refuse an assistant send to any role other than the user and the chief of staff, with both
|
||||
directions tested as refused and logged, and a role without the field unchanged." Read as: enforce what
|
||||
`roles/assistant.json` lists, hard-code no name (Sage accepted this in sage-0270). Sender direction: the
|
||||
assistant to pm, coder, scribe, warden and the tester, each refused and logged. Target direction: pm, coder and
|
||||
the tester to the assistant, each refused and logged. Allowed: assistant to human and chief, and human and chief
|
||||
to the assistant.
|
||||
|
||||
## Tests (`messaging.test.mjs`, 9)
|
||||
|
||||
Reads the shipped `roles/assistant.json`. Fixture instances: chief, assistant, pm, coder, scribe (send list
|
||||
only), warden (receive list only); `bus.tester: true`.
|
||||
|
||||
1. The assistant sends to the user and to the chief of staff only (allowed sends, both ways).
|
||||
2. An assistant send to any other role, or to the tester, is refused and logged (nothing stored, nothing consumed).
|
||||
3. Only the user and the chief of staff may send to the assistant; the tester is refused by default.
|
||||
4. A sender-side list alone refuses a send to a role that has no list.
|
||||
5. A target-side list alone refuses a send from a role that has no list.
|
||||
6. The sender list is checked before the target list.
|
||||
7. Roles without the field behave exactly as before.
|
||||
8. A business with no role definitions at all is unlimited.
|
||||
9. A malformed `messaging` field refuses at boot (`invalid-messaging`).
|
||||
|
||||
## Gate (decision 136, targeted; slot held per sage-0272)
|
||||
|
||||
Worktree `/mnt/storage/scratch/researcher/q120` at 85540a74e plus `row120.patch`. Outputs are in `suites/`.
|
||||
|
||||
| Run | Result |
|
||||
|---|---|
|
||||
| business | 80 / 80 |
|
||||
| bus | 165 / 165 |
|
||||
| harness | 84 / 84 |
|
||||
| cli | 191 / 191 |
|
||||
| webui | 108 / 108 |
|
||||
| tasks | 92 / 92 |
|
||||
| conversation | 182 / 182 |
|
||||
| seat | 132 / 132 |
|
||||
| `scripts/test-task.sh` | 98 passed, 0 failed |
|
||||
|
||||
No flake from #258, #261, #262 or #263 recurred.
|
||||
|
||||
## Mutants (`mutants.sh`, `mutants-run.txt`, `mutants/`)
|
||||
|
||||
Three controls pass (messaging 9/9, role 14/14, assistant 8/8). 19 mutants, each one exact-once edit, each run
|
||||
against the named test files. All 19 are caught; none survived. Per-mutant output, return code and patch are in
|
||||
`mutants/`.
|
||||
|
||||
| Mutant | Edit | Caught by |
|
||||
|---|---|---|
|
||||
| m1 | drop the sender-side check | messaging (3 tests) |
|
||||
| m2 | drop the target-side check | messaging (2 tests) |
|
||||
| m3 | drop the refusal log | messaging (5 tests) |
|
||||
| m4 | sender reads `receiveFrom` | messaging (2 tests) |
|
||||
| m5 | target reads `sendTo` | messaging (1 test) |
|
||||
| m6 | tester exempt from the target list | messaging (2 tests) |
|
||||
| m7 | role with no field refused on the sender side | messaging (4 tests) |
|
||||
| m8 | role with no field refused on the target side | messaging (5 tests) |
|
||||
| m9 | target matched by instance name | messaging (1 test) |
|
||||
| m10 | constructor shape unchecked | messaging (boot test) |
|
||||
| m11 | validator accepts `tester` | role test |
|
||||
| m12a, m12b | loader drops the field | role test; assistant test |
|
||||
| m13a, m13b | assistant `sendTo` widened with pm | messaging; assistant |
|
||||
| m14a, m14b | assistant `receiveFrom` widened with coder | messaging; assistant |
|
||||
| m15a, m15b | assistant file unlimited | messaging; assistant |
|
||||
|
||||
## Assumptions
|
||||
|
||||
- Entries are role definition names, not instance names. An org that renames the instance `chief` still limits
|
||||
the assistant to the instance whose definition is `chief-of-staff`. The caveat: two instances of one
|
||||
definition are treated alike.
|
||||
- `tester` can't be listed in a role file; it is refused by default wherever a list exists.
|
||||
- Limiting any other shipped role is a policy change of its own. The chief of staff and PM stay open, and
|
||||
`assistant.test.mjs` pins that.
|
||||
|
||||
## Out of scope and follow-ups
|
||||
|
||||
- "In the thread" (the assistant sends in the user's thread) lands in row 121.
|
||||
- A DECISION message that `decision.resolve` posts to the raising role doesn't go through `message.send`, so
|
||||
this row doesn't gate it. The assistant holds no decision authority, so it can't be the target of one today.
|
||||
If a role ever gains a `receiveFrom` list and decision authority, revisit.
|
||||
- Row 122's limits block and this row's checks are independent (limits count, messaging filters).
|
||||
|
||||
## Files in this packet
|
||||
|
||||
`row120.patch`, `candidate-manifest.sha256` (the 6 candidate files), `build.md`, `suites.sh`, `suites/`,
|
||||
`mutants.sh`, `mutants-run.txt`, `mutants/`, `packet-manifest.sha256` (digests of the packet files).
|
||||
@@ -0,0 +1,6 @@
|
||||
c9cc5a3b2a1ace84b2fcef8849a940118d30e57f4709dd98b284bd625f674da6 packages/bus/src/broker.mjs
|
||||
d756e762a696f40f102199d682570554c98c2ec1595a62c4fab511c35b77e48e packages/business/src/role.mjs
|
||||
be21e269988255078825fb6dfb1431061378d77226b8f58801266c48169a8c03 roles/assistant.json
|
||||
6a18989c1c467a94bba8d64f5267c43088e30826e94534f6c6cf5dc798b6642a packages/business/tests/role.test.mjs
|
||||
a1cf8abc42b64f679aedd9bc9a64b032f003d23a660f1b044de428e831bc28b1 packages/business/tests/assistant.test.mjs
|
||||
ed589e8f064325917d20dd7c8355910485da2b130f67b3ed511a3a385d8e3ae2 packages/bus/tests/messaging.test.mjs
|
||||
@@ -0,0 +1,84 @@
|
||||
control-messaging: exit 0; ℹ tests 9 ℹ pass 9 ℹ fail 0
|
||||
control-role: exit 0; ℹ tests 14 ℹ pass 14 ℹ fail 0
|
||||
control-assistant: exit 0; ℹ tests 8 ℹ pass 8 ℹ fail 0
|
||||
m1-drop-sender-side-check: exit 1; ℹ tests 9 ℹ pass 6 ℹ fail 3
|
||||
failed: ✖ an assistant send to any other role, or to the tester, is refused and logged
|
||||
failed: ✖ a sender-side list alone refuses a send to a role that has no list
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ the sender list is checked before the target list
|
||||
m2-drop-target-side-check: exit 1; ℹ tests 9 ℹ pass 7 ℹ fail 2
|
||||
failed: ✖ a target-side list alone refuses a send from a role that has no list
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ only the user and the chief of staff may send to the assistant; the tester is refused by default
|
||||
m3-drop-refusal-log: exit 1; ℹ tests 9 ℹ pass 4 ℹ fail 5
|
||||
failed: ✖ an assistant send to any other role, or to the tester, is refused and logged
|
||||
failed: ✖ a sender-side list alone refuses a send to a role that has no list
|
||||
failed: ✖ a target-side list alone refuses a send from a role that has no list
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ only the user and the chief of staff may send to the assistant; the tester is refused by default
|
||||
failed: ✖ the sender list is checked before the target list
|
||||
m4-sender-reads-receive-list: exit 1; ℹ tests 9 ℹ pass 7 ℹ fail 2
|
||||
failed: ✖ a sender-side list alone refuses a send to a role that has no list
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ the sender list is checked before the target list
|
||||
m5-target-reads-send-list: exit 1; ℹ tests 9 ℹ pass 8 ℹ fail 1
|
||||
failed: ✖ a target-side list alone refuses a send from a role that has no list
|
||||
failed: ✖ failing tests:
|
||||
m6-tester-exempt: exit 1; ℹ tests 9 ℹ pass 7 ℹ fail 2
|
||||
failed: ✖ a target-side list alone refuses a send from a role that has no list
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ only the user and the chief of staff may send to the assistant; the tester is refused by default
|
||||
m7-no-field-refuses-sender: exit 1; ℹ tests 9 ℹ pass 5 ℹ fail 4
|
||||
failed: ✖ a business with no role definitions at all is unlimited
|
||||
failed: ✖ a target-side list alone refuses a send from a role that has no list
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ only the user and the chief of staff may send to the assistant; the tester is refused by default
|
||||
failed: ✖ roles without the field behave exactly as before
|
||||
m8-no-field-refuses-target: exit 1; ℹ tests 9 ℹ pass 4 ℹ fail 5
|
||||
failed: ✖ a business with no role definitions at all is unlimited
|
||||
failed: ✖ an assistant send to any other role, or to the tester, is refused and logged
|
||||
failed: ✖ a sender-side list alone refuses a send to a role that has no list
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ roles without the field behave exactly as before
|
||||
failed: ✖ the assistant sends to the user and to the chief of staff only
|
||||
m9-target-by-instance-name: exit 1; ℹ tests 9 ℹ pass 8 ℹ fail 1
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ the assistant sends to the user and to the chief of staff only
|
||||
m10-boot-shape-unchecked: exit 1; ℹ tests 9 ℹ pass 8 ℹ fail 1
|
||||
failed: ✖ a malformed messaging field refuses at boot
|
||||
failed: ✖ failing tests:
|
||||
m11-validator-accepts-tester: exit 1; ℹ tests 14 ℹ pass 13 ℹ fail 1
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ messaging: optional, role definitions and human, never the tester
|
||||
m12a-loader-drops-field: exit 1; ℹ tests 14 ℹ pass 13 ℹ fail 1
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ messaging: optional, role definitions and human, never the tester
|
||||
m12b-loader-drops-field: exit 1; ℹ tests 8 ℹ pass 7 ℹ fail 1
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ the assistant talks to the user and the chief of staff only, and no other shipped role is limited
|
||||
m13a-assistant-sendTo-pm: exit 1; ℹ tests 9 ℹ pass 7 ℹ fail 2
|
||||
failed: ✖ an assistant send to any other role, or to the tester, is refused and logged
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ the assistant sends to the user and to the chief of staff only
|
||||
m13b-assistant-sendTo-pm: exit 1; ℹ tests 8 ℹ pass 6 ℹ fail 2
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ the assistant talks to the user and the chief of staff only, and no other shipped role is limited
|
||||
failed: ✖ the shipped file is exactly the approved list
|
||||
m14a-assistant-receiveFrom-coder: exit 1; ℹ tests 9 ℹ pass 7 ℹ fail 2
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ only the user and the chief of staff may send to the assistant; the tester is refused by default
|
||||
failed: ✖ the assistant sends to the user and to the chief of staff only
|
||||
m14b-assistant-receiveFrom-coder: exit 1; ℹ tests 8 ℹ pass 6 ℹ fail 2
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ the assistant talks to the user and the chief of staff only, and no other shipped role is limited
|
||||
failed: ✖ the shipped file is exactly the approved list
|
||||
m15a-assistant-unlimited: exit 1; ℹ tests 9 ℹ pass 6 ℹ fail 3
|
||||
failed: ✖ an assistant send to any other role, or to the tester, is refused and logged
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ only the user and the chief of staff may send to the assistant; the tester is refused by default
|
||||
failed: ✖ the assistant sends to the user and to the chief of staff only
|
||||
m15b-assistant-unlimited: exit 1; ℹ tests 8 ℹ pass 6 ℹ fail 2
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ the assistant talks to the user and the chief of staff only, and no other shipped role is limited
|
||||
failed: ✖ the shipped file is exactly the approved list
|
||||
done
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 120 (#1614): the broker limits who the assistant sends to and hears from.
|
||||
# Each check must fail on a mutant. For each mutant this runs one named test file against the
|
||||
# real source (control) and against the source with one exact-once text edit, in the given worktree,
|
||||
# one test file at a time, and restores every source file on exit.
|
||||
# Usage: mutants.sh <worktree> <outdir>
|
||||
set -u
|
||||
WT=$(cd "$1" && pwd); OUT=$(mkdir -p "$2" && cd "$2" && pwd)
|
||||
cd "$WT" || exit 2
|
||||
BAK=$(mktemp -d "$OUT/.bak.XXXXXX")
|
||||
declare -A SAVED
|
||||
save() { [ -n "${SAVED[$1]:-}" ] || { mkdir -p "$BAK/$(dirname "$1")"; cp "$1" "$BAK/$1"; SAVED[$1]=1; }; }
|
||||
restore() { for f in "${!SAVED[@]}"; do cp "$BAK/$f" "$f"; done; rm -rf "$BAK"; }
|
||||
trap restore EXIT
|
||||
edit() { # file old new: exact-once replace
|
||||
save "$1"
|
||||
node -e '
|
||||
const fs = require("fs"); const [f, a, b] = process.argv.slice(1);
|
||||
const s = fs.readFileSync(f, "utf8"); const n = s.split(a).length - 1;
|
||||
if (n !== 1) { console.error("expected one match, found " + n); process.exit(3); }
|
||||
fs.writeFileSync(f, s.replace(a, () => b));
|
||||
' "$1" "$2" "$3"
|
||||
}
|
||||
run() { # name testfile
|
||||
setsid -w timeout -k 10 400 node --test "$2" > "$OUT/$1.out" 2>&1; echo "$?" > "$OUT/$1.rc"
|
||||
echo "$1: exit $(cat "$OUT/$1.rc"); $(grep -E '^ℹ (tests|pass|fail)' "$OUT/$1.out" | tr '\n' ' ')"
|
||||
grep -E '^✖' "$OUT/$1.out" | sort -u | sed 's/ ([0-9.]*ms)$//' | sed "s/^/ failed: /"
|
||||
}
|
||||
mutant() { # name testfile file old new
|
||||
edit "$3" "$4" "$5" || { echo "$1: edit failed"; return; }
|
||||
diff -u "$BAK/$3" "$3" > "$OUT/$1.patch"
|
||||
run "$1" "$2"
|
||||
cp "$BAK/$3" "$3"
|
||||
}
|
||||
BT=packages/business/tests; MSG=packages/bus/tests/messaging.test.mjs
|
||||
ROLE=$BT/role.test.mjs; AST=$BT/assistant.test.mjs
|
||||
B=packages/bus/src/broker.mjs; R=packages/business/src/role.mjs; A=roles/assistant.json
|
||||
for t in "$MSG" "$ROLE" "$AST"; do run "control-$(basename "$t" .test.mjs)" "$t"; done
|
||||
|
||||
# m1: drop the sender-side check (the sender's sendTo)
|
||||
mutant m1-drop-sender-side-check "$MSG" "$B" "if (out && !out.sendTo.includes(target)) fail('message-recipient-refused');" "if (false) fail('message-recipient-refused');"
|
||||
# m2: drop the target-side check (the target's receiveFrom)
|
||||
mutant m2-drop-target-side-check "$MSG" "$B" "if (inbound && !inbound.receiveFrom.includes(sender)) fail('message-sender-refused');" "if (false) fail('message-sender-refused');"
|
||||
# m3: drop the refusal log for these two refusals
|
||||
mutant m3-drop-refusal-log "$MSG" "$B" "const error = e instanceof BusError ? e : new BusError(other);" "const error = e instanceof BusError ? e : new BusError(other);
|
||||
if (error.code.startsWith('message-')) return error;"
|
||||
# m4, m5: each direction reads the other direction's list
|
||||
mutant m4-sender-reads-receive-list "$MSG" "$B" "out.sendTo.includes(target)" "out.receiveFrom.includes(target)"
|
||||
mutant m5-target-reads-send-list "$MSG" "$B" "inbound.receiveFrom.includes(sender)" "inbound.sendTo.includes(sender)"
|
||||
# m6: the tester is exempt from the target's receive list
|
||||
mutant m6-tester-exempt "$MSG" "$B" "if (inbound && !inbound.receiveFrom" "if (inbound && !s.tester && !inbound.receiveFrom"
|
||||
# m7, m8: a role without the field is refused instead of unchanged (each direction)
|
||||
mutant m7-no-field-refuses-sender "$MSG" "$B" "definition(s.role)?.messaging;" "definition(s.role)?.messaging ?? { sendTo: [], receiveFrom: [] };"
|
||||
mutant m8-no-field-refuses-target "$MSG" "$B" "definition(to)?.messaging;" "definition(to)?.messaging ?? { sendTo: [], receiveFrom: [] };"
|
||||
# m9: the lists are matched on instance names, not definition names
|
||||
mutant m9-target-by-instance-name "$MSG" "$B" ": b.roles[to]?.definition;" ": to;"
|
||||
# m10: the constructor stops checking the shape
|
||||
mutant m10-boot-shape-unchecked "$MSG" "$B" "fail('invalid-messaging');" "void 0;"
|
||||
# m11: the validator accepts the tester in a list
|
||||
mutant m11-validator-accepts-tester "$ROLE" "$R" 'if (entry === "tester") refuse(' 'if (false) refuse('
|
||||
# m12: the validator drops the field from the loaded role
|
||||
for pair in "a $ROLE" "b $AST"; do
|
||||
set -- $pair
|
||||
mutant "m12$1-loader-drops-field" "$2" "$R" 'messaging: document.messaging === undefined ? null : checkMessaging(document.messaging),' 'messaging: null,'
|
||||
done
|
||||
# m13: the shipped assistant file lets it send to the PM, or hear from the coder
|
||||
for pair in "a $MSG" "b $AST"; do
|
||||
set -- $pair
|
||||
mutant "m13$1-assistant-sendTo-pm" "$2" "$A" '"sendTo": ["human", "chief-of-staff"]' '"sendTo": ["human", "chief-of-staff", "pm"]'
|
||||
done
|
||||
for pair in "a $MSG" "b $AST"; do
|
||||
set -- $pair
|
||||
mutant "m14$1-assistant-receiveFrom-coder" "$2" "$A" '"receiveFrom": ["human", "chief-of-staff"]' '"receiveFrom": ["human", "chief-of-staff", "coder"]'
|
||||
done
|
||||
# m15: the shipped assistant file has no messaging field at all
|
||||
for pair in "a $MSG" "b $AST"; do
|
||||
set -- $pair
|
||||
mutant "m15$1-assistant-unlimited" "$2" "$A" '"credentials": [],
|
||||
"messaging": {
|
||||
"sendTo": ["human", "chief-of-staff"],
|
||||
"receiveFrom": ["human", "chief-of-staff"]
|
||||
}
|
||||
}' '"credentials": []
|
||||
}'
|
||||
done
|
||||
echo done
|
||||
@@ -0,0 +1,16 @@
|
||||
✔ the role file loads and resolves through loadBusiness (5.025749ms)
|
||||
✔ the shipped file is exactly the approved list (0.628134ms)
|
||||
✔ the assistant's authority is message.send within role and nothing else (1.389008ms)
|
||||
✔ the assistant talks to the user and the chief of staff only, and no other shipped role is limited (0.490198ms)
|
||||
✔ a business grant through limits does not widen the role (1.095085ms)
|
||||
✔ a launch block whose by is the assistant is refused (1.149384ms)
|
||||
✔ the instance takes no credential references and no tracker (1.875469ms)
|
||||
✔ the contract passes work to the chief of staff and directs no one (0.452619ms)
|
||||
ℹ tests 8
|
||||
ℹ suites 0
|
||||
ℹ pass 8
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 66.143037
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,17 @@
|
||||
✔ the assistant sends to the user and to the chief of staff only (27.538171ms)
|
||||
✔ an assistant send to any other role, or to the tester, is refused and logged (23.196209ms)
|
||||
✔ only the user and the chief of staff may send to the assistant; the tester is refused by default (19.913919ms)
|
||||
✔ a sender-side list alone refuses a send to a role that has no list (15.839581ms)
|
||||
✔ a target-side list alone refuses a send from a role that has no list (16.275671ms)
|
||||
✔ the sender list is checked before the target list (14.353692ms)
|
||||
✔ roles without the field behave exactly as before (21.162362ms)
|
||||
✔ a business with no role definitions at all is unlimited (14.803918ms)
|
||||
✔ a malformed messaging field refuses at boot (7.089064ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 9
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 217.792497
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,22 @@
|
||||
✔ the four shipped version 2 roles load (2.531838ms)
|
||||
✔ shipped role scopes match addendum B section 2 and the SR runbook (0.835081ms)
|
||||
✔ shipped authority follows the note's table (0.476996ms)
|
||||
✔ every role file in roles/ loads through the validator, and a v2 contract exists (0.553728ms)
|
||||
✔ version 1 files keep loading with no authority (0.844998ms)
|
||||
✔ the conductor policy isn't a role (0.164697ms)
|
||||
✔ a missing role file is exit 4, a symbolic link too (0.370168ms)
|
||||
✔ version 2 refusals (1.000344ms)
|
||||
✔ authority: closed vocabulary, no gated-only action, no overlap (1.605938ms)
|
||||
✔ messaging: optional, role definitions and human, never the tester (1.199491ms)
|
||||
✔ credentials: Gitea scopes (0.934977ms)
|
||||
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (0.837613ms)
|
||||
✔ credentials: services (0.514901ms)
|
||||
✔ contract: a non-empty regular Markdown file beside the role file (0.703454ms)
|
||||
ℹ tests 14
|
||||
ℹ suites 0
|
||||
ℹ pass 14
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 75.812975
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,90 @@
|
||||
✔ the assistant sends to the user and to the chief of staff only (21.787903ms)
|
||||
✖ an assistant send to any other role, or to the tester, is refused and logged (16.192101ms)
|
||||
✔ only the user and the chief of staff may send to the assistant; the tester is refused by default (17.833444ms)
|
||||
✖ a sender-side list alone refuses a send to a role that has no list (14.625389ms)
|
||||
✔ a target-side list alone refuses a send from a role that has no list (16.956965ms)
|
||||
✖ the sender list is checked before the target list (14.945376ms)
|
||||
✔ roles without the field behave exactly as before (21.700633ms)
|
||||
✔ a business with no role definitions at all is unlimited (15.78649ms)
|
||||
✔ a malformed messaging field refuses at boot (7.562985ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 3
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 202.671166
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:88:1
|
||||
✖ an assistant send to any other role, or to the tester, is refused and logged (16.192101ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception: pm
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:94:35)
|
||||
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 startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'message-recipient-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:125:1
|
||||
✖ a sender-side list alone refuses a send to a role that has no list (14.625389ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception.
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:128:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'message-recipient-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:143:1
|
||||
✖ the sender list is checked before the target list (14.945376ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
Comparison {
|
||||
+ code: 'message-sender-refused'
|
||||
- code: 'message-recipient-refused'
|
||||
}
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:146: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: Error: message-sender-refused
|
||||
at fail (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:76:9)
|
||||
at #checkMessaging (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:338:59)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:969:29)
|
||||
at file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:701:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:695:26)
|
||||
at call (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:64:44)
|
||||
at send (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:66:5)
|
||||
at assert.throws.code (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:146:23)
|
||||
at getActual (node:assert:580:5),
|
||||
expected: { code: 'message-recipient-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/packages/bus/src/broker.mjs 2026-10-14 20:22:06.948510829 -0500
|
||||
+++ packages/bus/src/broker.mjs 2026-10-14 20:22:06.967292843 -0500
|
||||
@@ -333,7 +333,7 @@
|
||||
const sender = s.human ? 'human' : s.tester ? 'tester' : b.roles[s.role]?.definition;
|
||||
const target = to === 'human' || to === 'tester' ? to : b.roles[to]?.definition;
|
||||
const out = s.human || s.tester ? undefined : definition(s.role)?.messaging;
|
||||
- if (out && !out.sendTo.includes(target)) fail('message-recipient-refused');
|
||||
+ if (false) fail('message-recipient-refused');
|
||||
const inbound = to === 'human' || to === 'tester' ? undefined : definition(to)?.messaging;
|
||||
if (inbound && !inbound.receiveFrom.includes(sender)) fail('message-sender-refused');
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,37 @@
|
||||
✔ the assistant sends to the user and to the chief of staff only (21.403361ms)
|
||||
✔ an assistant send to any other role, or to the tester, is refused and logged (19.076295ms)
|
||||
✔ only the user and the chief of staff may send to the assistant; the tester is refused by default (18.146519ms)
|
||||
✔ a sender-side list alone refuses a send to a role that has no list (16.862936ms)
|
||||
✔ a target-side list alone refuses a send from a role that has no list (17.710285ms)
|
||||
✔ the sender list is checked before the target list (14.879144ms)
|
||||
✔ roles without the field behave exactly as before (22.021223ms)
|
||||
✔ a business with no role definitions at all is unlimited (15.278317ms)
|
||||
✖ a malformed messaging field refuses at boot (6.684574ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 8
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 212.467116
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:180:1
|
||||
✖ a malformed messaging field refuses at boot (6.684574ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception: "human"
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:201: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: undefined,
|
||||
expected: { code: 'invalid-messaging' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/packages/bus/src/broker.mjs 2026-10-14 20:22:06.948510829 -0500
|
||||
+++ packages/bus/src/broker.mjs 2026-10-14 20:22:09.305316657 -0500
|
||||
@@ -157,7 +157,7 @@
|
||||
(k) => Array.isArray(m[k]) && m[k].every((r) => id(r) && r !== 'tester'),
|
||||
)
|
||||
)
|
||||
- fail('invalid-messaging');
|
||||
+ void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,42 @@
|
||||
✔ the four shipped version 2 roles load (2.37926ms)
|
||||
✔ shipped role scopes match addendum B section 2 and the SR runbook (0.71676ms)
|
||||
✔ shipped authority follows the note's table (0.443335ms)
|
||||
✔ every role file in roles/ loads through the validator, and a v2 contract exists (0.544356ms)
|
||||
✔ version 1 files keep loading with no authority (0.848602ms)
|
||||
✔ the conductor policy isn't a role (0.145786ms)
|
||||
✔ a missing role file is exit 4, a symbolic link too (0.321409ms)
|
||||
✔ version 2 refusals (1.009939ms)
|
||||
✔ authority: closed vocabulary, no gated-only action, no overlap (1.692185ms)
|
||||
✖ messaging: optional, role definitions and human, never the tester (1.424799ms)
|
||||
✔ credentials: Gitea scopes (0.618981ms)
|
||||
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.056203ms)
|
||||
✔ credentials: services (0.375785ms)
|
||||
✔ contract: a non-empty regular Markdown file beside the role file (0.557179ms)
|
||||
ℹ tests 14
|
||||
ℹ suites 0
|
||||
ℹ pass 13
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 66.135454
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/business/tests/role.test.mjs:135:1
|
||||
✖ messaging: optional, role definitions and human, never the tester (1.424799ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception.
|
||||
at refuses (file:///mnt/storage/scratch/researcher/q120/packages/business/tests/role.test.mjs:9:10)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/business/tests/role.test.mjs:147:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/packages/business/src/role.mjs 2026-10-14 20:22:09.559150184 -0500
|
||||
+++ packages/business/src/role.mjs 2026-10-14 20:22:09.576319417 -0500
|
||||
@@ -58,7 +58,7 @@
|
||||
for (const key of ["sendTo", "receiveFrom"]) {
|
||||
lists[key] = requireDistinctList(messaging[key], `role messaging.${key}`, (entry) => {
|
||||
requireId(entry, `role messaging.${key} entry`);
|
||||
- if (entry === "tester") refuse(`messaging.${key} lists tester, which is never a role`);
|
||||
+ if (false) refuse(`messaging.${key} lists tester, which is never a role`);
|
||||
});
|
||||
}
|
||||
return lists;
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,53 @@
|
||||
✔ the four shipped version 2 roles load (2.339623ms)
|
||||
✔ shipped role scopes match addendum B section 2 and the SR runbook (0.720739ms)
|
||||
✔ shipped authority follows the note's table (0.475548ms)
|
||||
✔ every role file in roles/ loads through the validator, and a v2 contract exists (0.47729ms)
|
||||
✔ version 1 files keep loading with no authority (0.84248ms)
|
||||
✔ the conductor policy isn't a role (0.152063ms)
|
||||
✔ a missing role file is exit 4, a symbolic link too (0.305545ms)
|
||||
✔ version 2 refusals (0.961716ms)
|
||||
✔ authority: closed vocabulary, no gated-only action, no overlap (1.575172ms)
|
||||
✖ messaging: optional, role definitions and human, never the tester (1.194184ms)
|
||||
✔ credentials: Gitea scopes (0.609496ms)
|
||||
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.010414ms)
|
||||
✔ credentials: services (0.404915ms)
|
||||
✔ contract: a non-empty regular Markdown file beside the role file (0.494346ms)
|
||||
ℹ tests 14
|
||||
ℹ suites 0
|
||||
ℹ pass 13
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 69.541098
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/business/tests/role.test.mjs:135:1
|
||||
✖ messaging: optional, role definitions and human, never the tester (1.194184ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ null
|
||||
- {
|
||||
- receiveFrom: [],
|
||||
- sendTo: [
|
||||
- 'human',
|
||||
- 'chief-of-staff'
|
||||
- ]
|
||||
- }
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/business/tests/role.test.mjs:138: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: null,
|
||||
expected: { sendTo: [ 'human', 'chief-of-staff' ], receiveFrom: [] },
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/packages/business/src/role.mjs 2026-10-14 20:22:09.559150184 -0500
|
||||
+++ packages/business/src/role.mjs 2026-10-14 20:22:09.697320650 -0500
|
||||
@@ -152,7 +152,7 @@
|
||||
network: checkNetwork(document.network),
|
||||
authority: checkAuthority(document.authority),
|
||||
credentials: checkCredentials(document.credentials),
|
||||
- messaging: document.messaging === undefined ? null : checkMessaging(document.messaging),
|
||||
+ messaging: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,50 @@
|
||||
✔ the role file loads and resolves through loadBusiness (4.582399ms)
|
||||
✔ the shipped file is exactly the approved list (0.6061ms)
|
||||
✔ the assistant's authority is message.send within role and nothing else (1.429422ms)
|
||||
✖ the assistant talks to the user and the chief of staff only, and no other shipped role is limited (1.10325ms)
|
||||
✔ a business grant through limits does not widen the role (1.227914ms)
|
||||
✔ a launch block whose by is the assistant is refused (1.149985ms)
|
||||
✔ the instance takes no credential references and no tracker (2.048903ms)
|
||||
✔ the contract passes work to the chief of staff and directs no one (0.40784ms)
|
||||
ℹ tests 8
|
||||
ℹ suites 0
|
||||
ℹ pass 7
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 66.844399
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/business/tests/assistant.test.mjs:83:1
|
||||
✖ the assistant talks to the user and the chief of staff only, and no other shipped role is limited (1.10325ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ null
|
||||
- {
|
||||
- receiveFrom: [
|
||||
- 'human',
|
||||
- 'chief-of-staff'
|
||||
- ],
|
||||
- sendTo: [
|
||||
- 'human',
|
||||
- 'chief-of-staff'
|
||||
- ]
|
||||
- }
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/business/tests/assistant.test.mjs:85: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: null,
|
||||
expected: { sendTo: [ 'human', 'chief-of-staff' ], receiveFrom: [ 'human', 'chief-of-staff' ] },
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/packages/business/src/role.mjs 2026-10-14 20:22:09.559150184 -0500
|
||||
+++ packages/business/src/role.mjs 2026-10-14 20:22:09.825321954 -0500
|
||||
@@ -152,7 +152,7 @@
|
||||
network: checkNetwork(document.network),
|
||||
authority: checkAuthority(document.authority),
|
||||
credentials: checkCredentials(document.credentials),
|
||||
- messaging: document.messaging === undefined ? null : checkMessaging(document.messaging),
|
||||
+ messaging: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,67 @@
|
||||
✖ the assistant sends to the user and to the chief of staff only (20.769902ms)
|
||||
✖ an assistant send to any other role, or to the tester, is refused and logged (16.325872ms)
|
||||
✔ only the user and the chief of staff may send to the assistant; the tester is refused by default (18.475084ms)
|
||||
✔ a sender-side list alone refuses a send to a role that has no list (16.408895ms)
|
||||
✔ a target-side list alone refuses a send from a role that has no list (17.662367ms)
|
||||
✔ the sender list is checked before the target list (15.950453ms)
|
||||
✔ roles without the field behave exactly as before (25.309787ms)
|
||||
✔ a business with no role definitions at all is unlimited (16.157386ms)
|
||||
✔ a malformed messaging field refuses at boot (8.764075ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 7
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 215.618916
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:78:1
|
||||
✖ the assistant sends to the user and to the chief of staff only (20.769902ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
... Skipped lines
|
||||
|
||||
{
|
||||
receiveFrom: [
|
||||
'human',
|
||||
'chief-of-staff'
|
||||
],
|
||||
...
|
||||
'chief-of-staff',
|
||||
+ 'pm'
|
||||
]
|
||||
}
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:80:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: { sendTo: [ 'human', 'chief-of-staff', 'pm' ], receiveFrom: [ 'human', 'chief-of-staff' ] },
|
||||
expected: { sendTo: [ 'human', 'chief-of-staff' ], receiveFrom: [ 'human', 'chief-of-staff' ] },
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:88:1
|
||||
✖ an assistant send to any other role, or to the tester, is refused and logged (16.325872ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception: pm
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:94:35)
|
||||
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 startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'message-recipient-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/roles/assistant.json 2026-10-14 20:22:09.932589062 -0500
|
||||
+++ roles/assistant.json 2026-10-14 20:22:09.951323237 -0500
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"credentials": [],
|
||||
"messaging": {
|
||||
- "sendTo": ["human", "chief-of-staff"],
|
||||
+ "sendTo": ["human", "chief-of-staff", "pm"],
|
||||
"receiveFrom": ["human", "chief-of-staff"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,85 @@
|
||||
✔ the role file loads and resolves through loadBusiness (5.98655ms)
|
||||
✖ the shipped file is exactly the approved list (1.759309ms)
|
||||
✔ the assistant's authority is message.send within role and nothing else (1.866542ms)
|
||||
✖ the assistant talks to the user and the chief of staff only, and no other shipped role is limited (0.499937ms)
|
||||
✔ a business grant through limits does not widen the role (1.532646ms)
|
||||
✔ a launch block whose by is the assistant is refused (1.821923ms)
|
||||
✔ the instance takes no credential references and no tracker (2.235276ms)
|
||||
✔ the contract passes work to the chief of staff and directs no one (0.454049ms)
|
||||
ℹ tests 8
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 78.690293
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/business/tests/assistant.test.mjs:63:1
|
||||
✖ the shipped file is exactly the approved list (1.759309ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
... Skipped lines
|
||||
|
||||
{
|
||||
authority: {
|
||||
crossRole: [],
|
||||
withinRole: [
|
||||
'message.send'
|
||||
...
|
||||
'chief-of-staff',
|
||||
+ 'pm'
|
||||
]
|
||||
},
|
||||
name: 'assistant',
|
||||
network: 'api-only',
|
||||
roleVersion: 2,
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/business/tests/assistant.test.mjs:67: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 startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: { roleVersion: 2, name: 'assistant', contract: 'assistant.md', tools: [ 'read', 'grep', 'find', 'ls' ], network: 'api-only', authority: { withinRole: [Array], crossRole: [] }, credentials: [], messaging: { sendTo: [Array], receiveFrom: [Array] } },
|
||||
expected: { roleVersion: 2, name: 'assistant', contract: 'assistant.md', tools: [ 'read', 'grep', 'find', 'ls' ], network: 'api-only', authority: { withinRole: [Array], crossRole: [] }, credentials: [], messaging: { sendTo: [Array], receiveFrom: [Array] } },
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/business/tests/assistant.test.mjs:83:1
|
||||
✖ the assistant talks to the user and the chief of staff only, and no other shipped role is limited (0.499937ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
... Skipped lines
|
||||
|
||||
{
|
||||
receiveFrom: [
|
||||
'human',
|
||||
'chief-of-staff'
|
||||
],
|
||||
...
|
||||
'chief-of-staff',
|
||||
+ 'pm'
|
||||
]
|
||||
}
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/business/tests/assistant.test.mjs:85: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: { sendTo: [ 'human', 'chief-of-staff', 'pm' ], receiveFrom: [ 'human', 'chief-of-staff' ] },
|
||||
expected: { sendTo: [ 'human', 'chief-of-staff' ], receiveFrom: [ 'human', 'chief-of-staff' ] },
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/roles/assistant.json 2026-10-14 20:22:09.932589062 -0500
|
||||
+++ roles/assistant.json 2026-10-14 20:22:10.225326028 -0500
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"credentials": [],
|
||||
"messaging": {
|
||||
- "sendTo": ["human", "chief-of-staff"],
|
||||
+ "sendTo": ["human", "chief-of-staff", "pm"],
|
||||
"receiveFrom": ["human", "chief-of-staff"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,66 @@
|
||||
✖ the assistant sends to the user and to the chief of staff only (20.84959ms)
|
||||
✔ an assistant send to any other role, or to the tester, is refused and logged (23.432859ms)
|
||||
✖ only the user and the chief of staff may send to the assistant; the tester is refused by default (20.255769ms)
|
||||
✔ a sender-side list alone refuses a send to a role that has no list (17.734573ms)
|
||||
✔ a target-side list alone refuses a send from a role that has no list (19.809218ms)
|
||||
✔ the sender list is checked before the target list (17.466006ms)
|
||||
✔ roles without the field behave exactly as before (23.672218ms)
|
||||
✔ a business with no role definitions at all is unlimited (17.675763ms)
|
||||
✔ a malformed messaging field refuses at boot (8.83104ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 7
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 231.983215
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:78:1
|
||||
✖ the assistant sends to the user and to the chief of staff only (20.84959ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
{
|
||||
receiveFrom: [
|
||||
'human',
|
||||
'chief-of-staff',
|
||||
+ 'coder'
|
||||
],
|
||||
sendTo: [
|
||||
'human',
|
||||
'chief-of-staff'
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:80:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: { sendTo: [ 'human', 'chief-of-staff' ], receiveFrom: [ 'human', 'chief-of-staff', 'coder' ] },
|
||||
expected: { sendTo: [ 'human', 'chief-of-staff' ], receiveFrom: [ 'human', 'chief-of-staff' ] },
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:110:1
|
||||
✖ only the user and the chief of staff may send to the assistant; the tester is refused by default (20.255769ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception: coder
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:119:37)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'message-sender-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/roles/assistant.json 2026-10-14 20:22:09.932589062 -0500
|
||||
+++ roles/assistant.json 2026-10-14 20:22:10.367327475 -0500
|
||||
@@ -12,6 +12,6 @@
|
||||
"credentials": [],
|
||||
"messaging": {
|
||||
"sendTo": ["human", "chief-of-staff"],
|
||||
- "receiveFrom": ["human", "chief-of-staff"]
|
||||
+ "receiveFrom": ["human", "chief-of-staff", "coder"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,84 @@
|
||||
✔ the role file loads and resolves through loadBusiness (5.719343ms)
|
||||
✖ the shipped file is exactly the approved list (1.730876ms)
|
||||
✔ the assistant's authority is message.send within role and nothing else (1.704473ms)
|
||||
✖ the assistant talks to the user and the chief of staff only, and no other shipped role is limited (0.421776ms)
|
||||
✔ a business grant through limits does not widen the role (1.242733ms)
|
||||
✔ a launch block whose by is the assistant is refused (1.885922ms)
|
||||
✔ the instance takes no credential references and no tracker (2.664525ms)
|
||||
✔ the contract passes work to the chief of staff and directs no one (0.500417ms)
|
||||
ℹ tests 8
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 86.98472
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/business/tests/assistant.test.mjs:63:1
|
||||
✖ the shipped file is exactly the approved list (1.730876ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
... Skipped lines
|
||||
|
||||
{
|
||||
authority: {
|
||||
crossRole: [],
|
||||
withinRole: [
|
||||
'message.send'
|
||||
...
|
||||
'chief-of-staff',
|
||||
+ 'coder'
|
||||
],
|
||||
sendTo: [
|
||||
'human',
|
||||
'chief-of-staff'
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/business/tests/assistant.test.mjs:67: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 startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: { roleVersion: 2, name: 'assistant', contract: 'assistant.md', tools: [ 'read', 'grep', 'find', 'ls' ], network: 'api-only', authority: { withinRole: [Array], crossRole: [] }, credentials: [], messaging: { sendTo: [Array], receiveFrom: [Array] } },
|
||||
expected: { roleVersion: 2, name: 'assistant', contract: 'assistant.md', tools: [ 'read', 'grep', 'find', 'ls' ], network: 'api-only', authority: { withinRole: [Array], crossRole: [] }, credentials: [], messaging: { sendTo: [Array], receiveFrom: [Array] } },
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/business/tests/assistant.test.mjs:83:1
|
||||
✖ the assistant talks to the user and the chief of staff only, and no other shipped role is limited (0.421776ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
{
|
||||
receiveFrom: [
|
||||
'human',
|
||||
'chief-of-staff',
|
||||
+ 'coder'
|
||||
],
|
||||
sendTo: [
|
||||
'human',
|
||||
'chief-of-staff'
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/business/tests/assistant.test.mjs:85: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: { sendTo: [ 'human', 'chief-of-staff' ], receiveFrom: [ 'human', 'chief-of-staff', 'coder' ] },
|
||||
expected: { sendTo: [ 'human', 'chief-of-staff' ], receiveFrom: [ 'human', 'chief-of-staff' ] },
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/roles/assistant.json 2026-10-14 20:22:09.932589062 -0500
|
||||
+++ roles/assistant.json 2026-10-14 20:22:10.663330490 -0500
|
||||
@@ -12,6 +12,6 @@
|
||||
"credentials": [],
|
||||
"messaging": {
|
||||
"sendTo": ["human", "chief-of-staff"],
|
||||
- "receiveFrom": ["human", "chief-of-staff"]
|
||||
+ "receiveFrom": ["human", "chief-of-staff", "coder"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,85 @@
|
||||
✖ the assistant sends to the user and to the chief of staff only (23.868594ms)
|
||||
✖ an assistant send to any other role, or to the tester, is refused and logged (19.195695ms)
|
||||
✖ only the user and the chief of staff may send to the assistant; the tester is refused by default (18.400643ms)
|
||||
✔ a sender-side list alone refuses a send to a role that has no list (19.513217ms)
|
||||
✔ a target-side list alone refuses a send from a role that has no list (21.131124ms)
|
||||
✔ the sender list is checked before the target list (16.357066ms)
|
||||
✔ roles without the field behave exactly as before (23.902634ms)
|
||||
✔ a business with no role definitions at all is unlimited (16.737314ms)
|
||||
✔ a malformed messaging field refuses at boot (8.906114ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 3
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 241.137788
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:78:1
|
||||
✖ the assistant sends to the user and to the chief of staff only (23.868594ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ undefined
|
||||
- {
|
||||
- receiveFrom: [
|
||||
- 'human',
|
||||
- 'chief-of-staff'
|
||||
- ],
|
||||
- sendTo: [
|
||||
- 'human',
|
||||
- 'chief-of-staff'
|
||||
- ]
|
||||
- }
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:80:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { sendTo: [ 'human', 'chief-of-staff' ], receiveFrom: [ 'human', 'chief-of-staff' ] },
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:88:1
|
||||
✖ an assistant send to any other role, or to the tester, is refused and logged (19.195695ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception: pm
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:94:35)
|
||||
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 startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'message-recipient-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:110:1
|
||||
✖ only the user and the chief of staff may send to the assistant; the tester is refused by default (18.400643ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception: pm
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:119:37)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'message-sender-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/roles/assistant.json 2026-10-14 20:22:09.932589062 -0500
|
||||
+++ roles/assistant.json 2026-10-14 20:22:10.822332110 -0500
|
||||
@@ -9,9 +9,5 @@
|
||||
"withinRole": ["message.send"],
|
||||
"crossRole": []
|
||||
},
|
||||
- "credentials": [],
|
||||
- "messaging": {
|
||||
- "sendTo": ["human", "chief-of-staff"],
|
||||
- "receiveFrom": ["human", "chief-of-staff"]
|
||||
- }
|
||||
+ "credentials": []
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,94 @@
|
||||
✔ the role file loads and resolves through loadBusiness (4.841503ms)
|
||||
✖ the shipped file is exactly the approved list (1.562422ms)
|
||||
✔ the assistant's authority is message.send within role and nothing else (1.720263ms)
|
||||
✖ the assistant talks to the user and the chief of staff only, and no other shipped role is limited (0.396779ms)
|
||||
✔ a business grant through limits does not widen the role (1.271116ms)
|
||||
✔ a launch block whose by is the assistant is refused (1.369824ms)
|
||||
✔ the instance takes no credential references and no tracker (2.231741ms)
|
||||
✔ the contract passes work to the chief of staff and directs no one (0.503261ms)
|
||||
ℹ tests 8
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 77.349587
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/business/tests/assistant.test.mjs:63:1
|
||||
✖ the shipped file is exactly the approved list (1.562422ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
... Skipped lines
|
||||
|
||||
{
|
||||
authority: {
|
||||
crossRole: [],
|
||||
withinRole: [
|
||||
'message.send'
|
||||
...
|
||||
credentials: [],
|
||||
- messaging: {
|
||||
- receiveFrom: [
|
||||
- 'human',
|
||||
- 'chief-of-staff'
|
||||
- ],
|
||||
- sendTo: [
|
||||
- 'human',
|
||||
- 'chief-of-staff'
|
||||
- ]
|
||||
- },
|
||||
name: 'assistant',
|
||||
network: 'api-only',
|
||||
roleVersion: 2,
|
||||
tools: [
|
||||
'read',
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/business/tests/assistant.test.mjs:67: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 startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: { roleVersion: 2, name: 'assistant', contract: 'assistant.md', tools: [ 'read', 'grep', 'find', 'ls' ], network: 'api-only', authority: { withinRole: [Array], crossRole: [] }, credentials: [] },
|
||||
expected: { roleVersion: 2, name: 'assistant', contract: 'assistant.md', tools: [ 'read', 'grep', 'find', 'ls' ], network: 'api-only', authority: { withinRole: [Array], crossRole: [] }, credentials: [], messaging: { sendTo: [Array], receiveFrom: [Array] } },
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/business/tests/assistant.test.mjs:83:1
|
||||
✖ the assistant talks to the user and the chief of staff only, and no other shipped role is limited (0.396779ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ null
|
||||
- {
|
||||
- receiveFrom: [
|
||||
- 'human',
|
||||
- 'chief-of-staff'
|
||||
- ],
|
||||
- sendTo: [
|
||||
- 'human',
|
||||
- 'chief-of-staff'
|
||||
- ]
|
||||
- }
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/business/tests/assistant.test.mjs:85: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: null,
|
||||
expected: { sendTo: [ 'human', 'chief-of-staff' ], receiveFrom: [ 'human', 'chief-of-staff' ] },
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/roles/assistant.json 2026-10-14 20:22:09.932589062 -0500
|
||||
+++ roles/assistant.json 2026-10-14 20:22:11.127335217 -0500
|
||||
@@ -9,9 +9,5 @@
|
||||
"withinRole": ["message.send"],
|
||||
"crossRole": []
|
||||
},
|
||||
- "credentials": [],
|
||||
- "messaging": {
|
||||
- "sendTo": ["human", "chief-of-staff"],
|
||||
- "receiveFrom": ["human", "chief-of-staff"]
|
||||
- }
|
||||
+ "credentials": []
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,55 @@
|
||||
✔ the assistant sends to the user and to the chief of staff only (19.109502ms)
|
||||
✔ an assistant send to any other role, or to the tester, is refused and logged (18.653247ms)
|
||||
✖ only the user and the chief of staff may send to the assistant; the tester is refused by default (16.256796ms)
|
||||
✔ a sender-side list alone refuses a send to a role that has no list (15.960754ms)
|
||||
✖ a target-side list alone refuses a send from a role that has no list (15.363056ms)
|
||||
✔ the sender list is checked before the target list (13.870364ms)
|
||||
✔ roles without the field behave exactly as before (21.989586ms)
|
||||
✔ a business with no role definitions at all is unlimited (15.370555ms)
|
||||
✔ a malformed messaging field refuses at boot (7.531603ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 7
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 199.65459
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:110:1
|
||||
✖ only the user and the chief of staff may send to the assistant; the tester is refused by default (16.256796ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception: pm
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:119:37)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'message-sender-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:134:1
|
||||
✖ a target-side list alone refuses a send from a role that has no list (15.363056ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception: coder
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:138: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: undefined,
|
||||
expected: { code: 'message-sender-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/packages/bus/src/broker.mjs 2026-10-14 20:22:06.948510829 -0500
|
||||
+++ packages/bus/src/broker.mjs 2026-10-14 20:22:07.227295491 -0500
|
||||
@@ -335,7 +335,7 @@
|
||||
const out = s.human || s.tester ? undefined : definition(s.role)?.messaging;
|
||||
if (out && !out.sendTo.includes(target)) fail('message-recipient-refused');
|
||||
const inbound = to === 'human' || to === 'tester' ? undefined : definition(to)?.messaging;
|
||||
- if (inbound && !inbound.receiveFrom.includes(sender)) fail('message-sender-refused');
|
||||
+ if (false) fail('message-sender-refused');
|
||||
}
|
||||
#decision(s, id) {
|
||||
const d = this.#store.get('SELECT * FROM decisions WHERE id=? AND business=?', id, s.business);
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,199 @@
|
||||
✔ the assistant sends to the user and to the chief of staff only (19.820151ms)
|
||||
✖ an assistant send to any other role, or to the tester, is refused and logged (16.694624ms)
|
||||
✖ only the user and the chief of staff may send to the assistant; the tester is refused by default (15.38874ms)
|
||||
✖ a sender-side list alone refuses a send to a role that has no list (14.850664ms)
|
||||
✖ a target-side list alone refuses a send from a role that has no list (14.820406ms)
|
||||
✖ the sender list is checked before the target list (14.90961ms)
|
||||
✔ roles without the field behave exactly as before (23.530851ms)
|
||||
✔ a business with no role definitions at all is unlimited (15.506059ms)
|
||||
✔ a malformed messaging field refuses at boot (7.585977ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 5
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 199.817541
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:88:1
|
||||
✖ an assistant send to any other role, or to the tester, is refused and logged (16.694624ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ []
|
||||
- [
|
||||
- [
|
||||
- 'assistant',
|
||||
- 'message-recipient-refused'
|
||||
- ],
|
||||
- [
|
||||
- 'assistant',
|
||||
- 'message-recipient-refused'
|
||||
- ],
|
||||
- [
|
||||
- 'assistant',
|
||||
- 'message-recipient-refused'
|
||||
- ],
|
||||
- [
|
||||
- 'assistant',
|
||||
- 'message-recipient-refused'
|
||||
- ],
|
||||
- [
|
||||
- 'assistant',
|
||||
- 'message-recipient-refused'
|
||||
- ]
|
||||
- ]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:97: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 startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: [],
|
||||
expected: [ [ 'assistant', 'message-recipient-refused' ], [ 'assistant', 'message-recipient-refused' ], [ 'assistant', 'message-recipient-refused' ], [ 'assistant', 'message-recipient-refused' ], [ 'assistant', 'message-recipient-refused' ] ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:110:1
|
||||
✖ only the user and the chief of staff may send to the assistant; the tester is refused by default (15.38874ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ []
|
||||
- [
|
||||
- [
|
||||
- 'pm',
|
||||
- 'message-sender-refused'
|
||||
- ],
|
||||
- [
|
||||
- 'coder',
|
||||
- 'message-sender-refused'
|
||||
- ],
|
||||
- [
|
||||
- 'tester',
|
||||
- 'message-sender-refused'
|
||||
- ]
|
||||
- ]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:120:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: [],
|
||||
expected: [ [ 'pm', 'message-sender-refused' ], [ 'coder', 'message-sender-refused' ], [ 'tester', 'message-sender-refused' ] ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:125:1
|
||||
✖ a sender-side list alone refuses a send to a role that has no list (14.850664ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ []
|
||||
- [
|
||||
- [
|
||||
- 'scribe',
|
||||
- 'message-recipient-refused'
|
||||
- ],
|
||||
- [
|
||||
- 'scribe',
|
||||
- 'message-recipient-refused'
|
||||
- ]
|
||||
- ]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.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: [],
|
||||
expected: [ [ 'scribe', 'message-recipient-refused' ], [ 'scribe', 'message-recipient-refused' ] ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:134:1
|
||||
✖ a target-side list alone refuses a send from a role that has no list (14.820406ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ []
|
||||
- [
|
||||
- [
|
||||
- 'coder',
|
||||
- 'message-sender-refused'
|
||||
- ],
|
||||
- [
|
||||
- 'chief',
|
||||
- 'message-sender-refused'
|
||||
- ],
|
||||
- [
|
||||
- null,
|
||||
- 'message-sender-refused'
|
||||
- ],
|
||||
- [
|
||||
- 'tester',
|
||||
- 'message-sender-refused'
|
||||
- ]
|
||||
- ]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:139:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: [],
|
||||
expected: [ [ 'coder', 'message-sender-refused' ], [ 'chief', 'message-sender-refused' ], [ null, 'message-sender-refused' ], [ 'tester', 'message-sender-refused' ] ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:143:1
|
||||
✖ the sender list is checked before the target list (14.90961ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ []
|
||||
- [
|
||||
- [
|
||||
- 'scribe',
|
||||
- 'message-recipient-refused'
|
||||
- ]
|
||||
- ]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:147:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: [],
|
||||
expected: [ [ 'scribe', 'message-recipient-refused' ] ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/packages/bus/src/broker.mjs 2026-10-14 20:22:06.948510829 -0500
|
||||
+++ packages/bus/src/broker.mjs 2026-10-14 20:22:07.477298037 -0500
|
||||
@@ -665,6 +665,7 @@
|
||||
}
|
||||
#refused(s, e, request, other = 'storage-refused') {
|
||||
const error = e instanceof BusError ? e : new BusError(other);
|
||||
+ if (error.code.startsWith('message-')) return error;
|
||||
// No request content or raw exception text in refusal evidence.
|
||||
try {
|
||||
this.#store.transaction(() =>
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,72 @@
|
||||
✔ the assistant sends to the user and to the chief of staff only (20.09624ms)
|
||||
✔ an assistant send to any other role, or to the tester, is refused and logged (18.849682ms)
|
||||
✔ only the user and the chief of staff may send to the assistant; the tester is refused by default (17.758286ms)
|
||||
✖ a sender-side list alone refuses a send to a role that has no list (16.09345ms)
|
||||
✔ a target-side list alone refuses a send from a role that has no list (17.984013ms)
|
||||
✖ the sender list is checked before the target list (15.730753ms)
|
||||
✔ roles without the field behave exactly as before (22.334483ms)
|
||||
✔ a business with no role definitions at all is unlimited (15.701319ms)
|
||||
✔ a malformed messaging field refuses at boot (7.02954ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 7
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 211.857974
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:125:1
|
||||
✖ a sender-side list alone refuses a send to a role that has no list (16.09345ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception.
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:128:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'message-recipient-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:143:1
|
||||
✖ the sender list is checked before the target list (15.730753ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
Comparison {
|
||||
+ code: 'message-sender-refused'
|
||||
- code: 'message-recipient-refused'
|
||||
}
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:146: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: Error: message-sender-refused
|
||||
at fail (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:76:9)
|
||||
at #checkMessaging (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:338:59)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:969:29)
|
||||
at file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:701:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:695:26)
|
||||
at call (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:64:44)
|
||||
at send (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:66:5)
|
||||
at assert.throws.code (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:146:23)
|
||||
at getActual (node:assert:580:5),
|
||||
expected: { code: 'message-recipient-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/packages/bus/src/broker.mjs 2026-10-14 20:22:06.948510829 -0500
|
||||
+++ packages/bus/src/broker.mjs 2026-10-14 20:22:07.735300665 -0500
|
||||
@@ -333,7 +333,7 @@
|
||||
const sender = s.human ? 'human' : s.tester ? 'tester' : b.roles[s.role]?.definition;
|
||||
const target = to === 'human' || to === 'tester' ? to : b.roles[to]?.definition;
|
||||
const out = s.human || s.tester ? undefined : definition(s.role)?.messaging;
|
||||
- if (out && !out.sendTo.includes(target)) fail('message-recipient-refused');
|
||||
+ if (out && !out.receiveFrom.includes(target)) fail('message-recipient-refused');
|
||||
const inbound = to === 'human' || to === 'tester' ? undefined : definition(to)?.messaging;
|
||||
if (inbound && !inbound.receiveFrom.includes(sender)) fail('message-sender-refused');
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,37 @@
|
||||
✔ the assistant sends to the user and to the chief of staff only (19.959065ms)
|
||||
✔ an assistant send to any other role, or to the tester, is refused and logged (18.049066ms)
|
||||
✔ only the user and the chief of staff may send to the assistant; the tester is refused by default (17.532892ms)
|
||||
✔ a sender-side list alone refuses a send to a role that has no list (16.202063ms)
|
||||
✖ a target-side list alone refuses a send from a role that has no list (15.966992ms)
|
||||
✔ the sender list is checked before the target list (14.719369ms)
|
||||
✔ roles without the field behave exactly as before (21.882411ms)
|
||||
✔ a business with no role definitions at all is unlimited (15.716027ms)
|
||||
✔ a malformed messaging field refuses at boot (7.354747ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 8
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 206.581795
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:134:1
|
||||
✖ a target-side list alone refuses a send from a role that has no list (15.966992ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception: coder
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:138: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: undefined,
|
||||
expected: { code: 'message-sender-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/packages/bus/src/broker.mjs 2026-10-14 20:22:06.948510829 -0500
|
||||
+++ packages/bus/src/broker.mjs 2026-10-14 20:22:08.002303385 -0500
|
||||
@@ -335,7 +335,7 @@
|
||||
const out = s.human || s.tester ? undefined : definition(s.role)?.messaging;
|
||||
if (out && !out.sendTo.includes(target)) fail('message-recipient-refused');
|
||||
const inbound = to === 'human' || to === 'tester' ? undefined : definition(to)?.messaging;
|
||||
- if (inbound && !inbound.receiveFrom.includes(sender)) fail('message-sender-refused');
|
||||
+ if (inbound && !inbound.sendTo.includes(sender)) fail('message-sender-refused');
|
||||
}
|
||||
#decision(s, id) {
|
||||
const d = this.#store.get('SELECT * FROM decisions WHERE id=? AND business=?', id, s.business);
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,55 @@
|
||||
✔ the assistant sends to the user and to the chief of staff only (20.761108ms)
|
||||
✔ an assistant send to any other role, or to the tester, is refused and logged (18.599261ms)
|
||||
✖ only the user and the chief of staff may send to the assistant; the tester is refused by default (18.061049ms)
|
||||
✔ a sender-side list alone refuses a send to a role that has no list (16.343731ms)
|
||||
✖ a target-side list alone refuses a send from a role that has no list (17.855671ms)
|
||||
✔ the sender list is checked before the target list (14.644244ms)
|
||||
✔ roles without the field behave exactly as before (20.985724ms)
|
||||
✔ a business with no role definitions at all is unlimited (15.368113ms)
|
||||
✔ a malformed messaging field refuses at boot (7.00822ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 7
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 211.46733
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:110:1
|
||||
✖ only the user and the chief of staff may send to the assistant; the tester is refused by default (18.061049ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception: tester
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:119:37)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'message-sender-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:134:1
|
||||
✖ a target-side list alone refuses a send from a role that has no list (17.855671ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception: tester
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:138: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: undefined,
|
||||
expected: { code: 'message-sender-refused' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/packages/bus/src/broker.mjs 2026-10-14 20:22:06.948510829 -0500
|
||||
+++ packages/bus/src/broker.mjs 2026-10-14 20:22:08.266306074 -0500
|
||||
@@ -335,7 +335,7 @@
|
||||
const out = s.human || s.tester ? undefined : definition(s.role)?.messaging;
|
||||
if (out && !out.sendTo.includes(target)) fail('message-recipient-refused');
|
||||
const inbound = to === 'human' || to === 'tester' ? undefined : definition(to)?.messaging;
|
||||
- if (inbound && !inbound.receiveFrom.includes(sender)) fail('message-sender-refused');
|
||||
+ if (inbound && !s.tester && !inbound.receiveFrom.includes(sender)) fail('message-sender-refused');
|
||||
}
|
||||
#decision(s, id) {
|
||||
const d = this.#store.get('SELECT * FROM decisions WHERE id=? AND business=?', id, s.business);
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,83 @@
|
||||
✔ the assistant sends to the user and to the chief of staff only (20.013574ms)
|
||||
✔ an assistant send to any other role, or to the tester, is refused and logged (18.046013ms)
|
||||
✖ only the user and the chief of staff may send to the assistant; the tester is refused by default (14.850868ms)
|
||||
✔ a sender-side list alone refuses a send to a role that has no list (15.380157ms)
|
||||
✖ a target-side list alone refuses a send from a role that has no list (13.597071ms)
|
||||
✔ the sender list is checked before the target list (13.874663ms)
|
||||
✖ roles without the field behave exactly as before (13.094404ms)
|
||||
✖ a business with no role definitions at all is unlimited (13.891916ms)
|
||||
✔ a malformed messaging field refuses at boot (7.826936ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ fail 4
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 186.529051
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:110:1
|
||||
✖ only the user and the chief of staff may send to the assistant; the tester is refused by default (14.850868ms)
|
||||
Error: message-recipient-refused
|
||||
at fail (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:76:9)
|
||||
at #checkMessaging (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:336:46)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:969:29)
|
||||
at file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:701:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:695:26)
|
||||
at call (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:64:44)
|
||||
at send (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:66:5)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:113:18)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14) {
|
||||
code: 'message-recipient-refused'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:134:1
|
||||
✖ a target-side list alone refuses a send from a role that has no list (13.597071ms)
|
||||
Error: message-recipient-refused
|
||||
at fail (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:76:9)
|
||||
at #checkMessaging (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:336:46)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:969:29)
|
||||
at file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:701:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:695:26)
|
||||
at call (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:64:44)
|
||||
at send (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:66:5)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:136:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14) {
|
||||
code: 'message-recipient-refused'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:150:1
|
||||
✖ roles without the field behave exactly as before (13.094404ms)
|
||||
Error: message-recipient-refused
|
||||
at fail (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:76:9)
|
||||
at #checkMessaging (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:336:46)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:969:29)
|
||||
at file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:701:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:695:26)
|
||||
at call (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:64:44)
|
||||
at send (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:66:5)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:162:5)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14) {
|
||||
code: 'message-recipient-refused'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:172:1
|
||||
✖ a business with no role definitions at all is unlimited (13.891916ms)
|
||||
Error: message-recipient-refused
|
||||
at fail (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:76:9)
|
||||
at #checkMessaging (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:336:46)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:969:29)
|
||||
at file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:701:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:695:26)
|
||||
at call (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:64:44)
|
||||
at send (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:66:5)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:176:97)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14) {
|
||||
code: 'message-recipient-refused'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/packages/bus/src/broker.mjs 2026-10-14 20:22:06.948510829 -0500
|
||||
+++ packages/bus/src/broker.mjs 2026-10-14 20:22:08.535308814 -0500
|
||||
@@ -332,7 +332,7 @@
|
||||
const definition = (role) => b.definitions?.[b.roles[role]?.definition];
|
||||
const sender = s.human ? 'human' : s.tester ? 'tester' : b.roles[s.role]?.definition;
|
||||
const target = to === 'human' || to === 'tester' ? to : b.roles[to]?.definition;
|
||||
- const out = s.human || s.tester ? undefined : definition(s.role)?.messaging;
|
||||
+ const out = s.human || s.tester ? undefined : definition(s.role)?.messaging ?? { sendTo: [], receiveFrom: [] };
|
||||
if (out && !out.sendTo.includes(target)) fail('message-recipient-refused');
|
||||
const inbound = to === 'human' || to === 'tester' ? undefined : definition(to)?.messaging;
|
||||
if (inbound && !inbound.receiveFrom.includes(sender)) fail('message-sender-refused');
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,99 @@
|
||||
✖ the assistant sends to the user and to the chief of staff only (20.294334ms)
|
||||
✖ an assistant send to any other role, or to the tester, is refused and logged (15.232281ms)
|
||||
✔ only the user and the chief of staff may send to the assistant; the tester is refused by default (18.256702ms)
|
||||
✖ a sender-side list alone refuses a send to a role that has no list (14.003341ms)
|
||||
✔ a target-side list alone refuses a send from a role that has no list (17.138062ms)
|
||||
✔ the sender list is checked before the target list (15.313361ms)
|
||||
✖ roles without the field behave exactly as before (16.371822ms)
|
||||
✖ a business with no role definitions at all is unlimited (18.730179ms)
|
||||
✔ a malformed messaging field refuses at boot (10.166546ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 5
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 206.467275
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:78:1
|
||||
✖ the assistant sends to the user and to the chief of staff only (20.294334ms)
|
||||
Error: message-sender-refused
|
||||
at fail (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:76:9)
|
||||
at #checkMessaging (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:338:59)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:969:29)
|
||||
at file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:701:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:695:26)
|
||||
at call (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:64:44)
|
||||
at send (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:66:5)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:82:18)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14) {
|
||||
code: 'message-sender-refused'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:88:1
|
||||
✖ an assistant send to any other role, or to the tester, is refused and logged (15.232281ms)
|
||||
Error: message-sender-refused
|
||||
at fail (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:76:9)
|
||||
at #checkMessaging (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:338:59)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:969:29)
|
||||
at file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:701:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:695:26)
|
||||
at call (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:64:44)
|
||||
at send (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:66:5)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:90:17)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14) {
|
||||
code: 'message-sender-refused'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:125:1
|
||||
✖ a sender-side list alone refuses a send to a role that has no list (14.003341ms)
|
||||
Error: message-sender-refused
|
||||
at fail (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:76:9)
|
||||
at #checkMessaging (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:338:59)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:969:29)
|
||||
at file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:701:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:695:26)
|
||||
at call (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:64:44)
|
||||
at send (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:66:5)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:127:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14) {
|
||||
code: 'message-sender-refused'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:150:1
|
||||
✖ roles without the field behave exactly as before (16.371822ms)
|
||||
Error: message-sender-refused
|
||||
at fail (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:76:9)
|
||||
at #checkMessaging (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:338:59)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:969:29)
|
||||
at file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:701:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:695:26)
|
||||
at call (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:64:44)
|
||||
at send (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:66:5)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:162:5)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14) {
|
||||
code: 'message-sender-refused'
|
||||
}
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:172:1
|
||||
✖ a business with no role definitions at all is unlimited (18.730179ms)
|
||||
Error: message-sender-refused
|
||||
at fail (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:76:9)
|
||||
at #checkMessaging (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:338:59)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:969:29)
|
||||
at file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:701:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:695:26)
|
||||
at call (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:64:44)
|
||||
at send (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:66:5)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:176:97)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14) {
|
||||
code: 'message-sender-refused'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/packages/bus/src/broker.mjs 2026-10-14 20:22:06.948510829 -0500
|
||||
+++ packages/bus/src/broker.mjs 2026-10-14 20:22:08.776319285 -0500
|
||||
@@ -334,7 +334,7 @@
|
||||
const target = to === 'human' || to === 'tester' ? to : b.roles[to]?.definition;
|
||||
const out = s.human || s.tester ? undefined : definition(s.role)?.messaging;
|
||||
if (out && !out.sendTo.includes(target)) fail('message-recipient-refused');
|
||||
- const inbound = to === 'human' || to === 'tester' ? undefined : definition(to)?.messaging;
|
||||
+ const inbound = to === 'human' || to === 'tester' ? undefined : definition(to)?.messaging ?? { sendTo: [], receiveFrom: [] };
|
||||
if (inbound && !inbound.receiveFrom.includes(sender)) fail('message-sender-refused');
|
||||
}
|
||||
#decision(s, id) {
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,35 @@
|
||||
✖ the assistant sends to the user and to the chief of staff only (20.994601ms)
|
||||
✔ an assistant send to any other role, or to the tester, is refused and logged (18.964598ms)
|
||||
✔ only the user and the chief of staff may send to the assistant; the tester is refused by default (18.25924ms)
|
||||
✔ a sender-side list alone refuses a send to a role that has no list (16.346553ms)
|
||||
✔ a target-side list alone refuses a send from a role that has no list (17.434642ms)
|
||||
✔ the sender list is checked before the target list (15.414156ms)
|
||||
✔ roles without the field behave exactly as before (22.546149ms)
|
||||
✔ a business with no role definitions at all is unlimited (16.20482ms)
|
||||
✔ a malformed messaging field refuses at boot (7.705473ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 8
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 209.198363
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/messaging.test.mjs:78:1
|
||||
✖ the assistant sends to the user and to the chief of staff only (20.994601ms)
|
||||
Error: message-recipient-refused
|
||||
at fail (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:76:9)
|
||||
at #checkMessaging (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:336:46)
|
||||
at #dispatch (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:969:29)
|
||||
at file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:701:38
|
||||
at Store.transaction (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/store.mjs:156:22)
|
||||
at Broker.request (file:///mnt/storage/scratch/researcher/q120/packages/bus/src/broker.mjs:695:26)
|
||||
at call (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:64:44)
|
||||
at send (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:66:5)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q120/packages/bus/tests/messaging.test.mjs:82:18)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14) {
|
||||
code: 'message-recipient-refused'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-120/mutants/.bak.yvAdpu/packages/bus/src/broker.mjs 2026-10-14 20:22:06.948510829 -0500
|
||||
+++ packages/bus/src/broker.mjs 2026-10-14 20:22:09.038313937 -0500
|
||||
@@ -331,7 +331,7 @@
|
||||
const b = this.#business(s.business);
|
||||
const definition = (role) => b.definitions?.[b.roles[role]?.definition];
|
||||
const sender = s.human ? 'human' : s.tester ? 'tester' : b.roles[s.role]?.definition;
|
||||
- const target = to === 'human' || to === 'tester' ? to : b.roles[to]?.definition;
|
||||
+ const target = to === 'human' || to === 'tester' ? to : to;
|
||||
const out = s.human || s.tester ? undefined : definition(s.role)?.messaging;
|
||||
if (out && !out.sendTo.includes(target)) fail('message-recipient-refused');
|
||||
const inbound = to === 'human' || to === 'tester' ? undefined : definition(to)?.messaging;
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,78 @@
|
||||
0716725543024bc6aa727aca17cef9e507057078fcb4f4734ff7a97305c3071c ./build.md
|
||||
cbe2794506ad434b972c14bea74c9def4bee2b5478b7eba224987500e5e1dbce ./candidate-manifest.sha256
|
||||
3c4a5a2a0428ad104cba34ce024352a1518c9fea335309c89dec698bf9bacd29 ./mutants/control-assistant.out
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa ./mutants/control-assistant.rc
|
||||
ba63f7a68ccac8bda77a4b011943a54890f11f40291ff65aea18419fb245602b ./mutants/control-messaging.out
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa ./mutants/control-messaging.rc
|
||||
29864e2f3f56f5087691a9470a616b5c56d8294ba966426b138943e3c99202af ./mutants/control-role.out
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa ./mutants/control-role.rc
|
||||
ce0d8abf656dc468c60be6f56c0648652c54a632a10d6ee4140f200d7c1a2c2d ./mutants/m10-boot-shape-unchecked.out
|
||||
707278bc5e7f68a7cd664c6ec0fd9723bbd6271dd9f927bd35bb8bd2b5c6fd4d ./mutants/m10-boot-shape-unchecked.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m10-boot-shape-unchecked.rc
|
||||
28d4062c6d599bb7b9ce4b6942444941f9d7c5127ff48089f8b8f2047ffc9866 ./mutants/m11-validator-accepts-tester.out
|
||||
1d0c33c69374bb1940924443fa5fdde19bfc980dbc7d46e3d8229e09b3d5cb63 ./mutants/m11-validator-accepts-tester.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m11-validator-accepts-tester.rc
|
||||
133403c7acfaff97577bf618aaabbd41764ebb95c98caa10d9dbb1fe2fca654a ./mutants/m12a-loader-drops-field.out
|
||||
dd03bc08f1380dd84fc066c36367233c1a8739c3b1d7a147c54a6d039e004208 ./mutants/m12a-loader-drops-field.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m12a-loader-drops-field.rc
|
||||
f30598ca7554eb67417db43621a52e4406a7a83e795ca4c9983ff1766841fa8b ./mutants/m12b-loader-drops-field.out
|
||||
e13182fd9692ff530986752cf0e468c35ab69f8884d6b512dcecf7d3e8ddd18a ./mutants/m12b-loader-drops-field.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m12b-loader-drops-field.rc
|
||||
61bd44b4d6cc4981ae77188b232c661095aeec62c76df9f1afb282676ae23f72 ./mutants/m13a-assistant-sendTo-pm.out
|
||||
eba88f196654216b21b2f4a8cd9bd9bc512eaa00027347b0f03ca550409fc7f9 ./mutants/m13a-assistant-sendTo-pm.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m13a-assistant-sendTo-pm.rc
|
||||
49208663921b4ea13193d8d3c1cb07d761f085868320db975fcfb9790b901e1a ./mutants/m13b-assistant-sendTo-pm.out
|
||||
2010b3b5ec2a79c028f8083622d31a0f12c21c2a7a5c53c8d891c7b87b5bf7f5 ./mutants/m13b-assistant-sendTo-pm.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m13b-assistant-sendTo-pm.rc
|
||||
98653e82d61a2928000c6992321b6d5aa8b77378e3fb5547a88a4ef61ea20fa5 ./mutants/m14a-assistant-receiveFrom-coder.out
|
||||
269ae21f8324a7671c814869f7785b4c5a1ff8baeeb5c283e9de12a8335c20ed ./mutants/m14a-assistant-receiveFrom-coder.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m14a-assistant-receiveFrom-coder.rc
|
||||
247042483605cb76c9de9514c45cb64ce27f1b93b12efcada84d90b381bc7379 ./mutants/m14b-assistant-receiveFrom-coder.out
|
||||
fc6abdcc137b9b889405a3254f0cf673d7c7901c157acf1454a8e4fdbefe6b31 ./mutants/m14b-assistant-receiveFrom-coder.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m14b-assistant-receiveFrom-coder.rc
|
||||
1fac9f1ac650ccabe7f60c2bfb1c674d3668ff646db726de159e51b7a109fbe4 ./mutants/m15a-assistant-unlimited.out
|
||||
8b561aa012a382c6c1c8f0c5b9a3bce909e1aef44ee5a5de70001e6619bcb799 ./mutants/m15a-assistant-unlimited.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m15a-assistant-unlimited.rc
|
||||
347b09c9afa418582c28d4a6db8f59da85e0cc3ca699f1039eba0565bd0ccd58 ./mutants/m15b-assistant-unlimited.out
|
||||
5ead81433005b28ea02122874f6a0240d8f1e420628bfa501a6ff0a89f2dd528 ./mutants/m15b-assistant-unlimited.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m15b-assistant-unlimited.rc
|
||||
efba9c03493787aa0c1f739c150d5cb5404b54c54bd6c0f7d96e205c2ee5a77b ./mutants/m1-drop-sender-side-check.out
|
||||
2753279f077ba063b7859aec6a1f6a7874172bb85b772104126828979526ae3f ./mutants/m1-drop-sender-side-check.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m1-drop-sender-side-check.rc
|
||||
b439643c2126a735cba13f5ef7557e58791fca62a05c8f5c62f4178820646aa6 ./mutants/m2-drop-target-side-check.out
|
||||
a1d04100aed36befffcb8ca18a3437d1ecb40039c9856575a04b907aa4bd89af ./mutants/m2-drop-target-side-check.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m2-drop-target-side-check.rc
|
||||
0bc239bfdb02243057d2df29fbe430e36bf7290dea06c74fccaa4386a5aff85a ./mutants/m3-drop-refusal-log.out
|
||||
bc5ac544c64145b706c91e40199bbb5f12ee68ccb853ef0b067c8a1b1405038d ./mutants/m3-drop-refusal-log.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m3-drop-refusal-log.rc
|
||||
7987148145039a008e360f991a542d242c5ec65463ebc69a8579cabaf2289ba8 ./mutants/m4-sender-reads-receive-list.out
|
||||
8e711305da0886541c37077f06d07fbe5ab2b457158274ee284df0d43c208677 ./mutants/m4-sender-reads-receive-list.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m4-sender-reads-receive-list.rc
|
||||
f0daf3c6f934d05a17525dd76131c67e1bdf2ff0735c3da7fdd7ae42a4886766 ./mutants/m5-target-reads-send-list.out
|
||||
3c11d716a49065746f05d405e2c391a35f12b059f8a1057126bde350335a6ddd ./mutants/m5-target-reads-send-list.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m5-target-reads-send-list.rc
|
||||
12f13f7556d2ab75741f55af2d782adea1b8e7edc8cddb2e7e8413afb9049dd6 ./mutants/m6-tester-exempt.out
|
||||
f1405fef34b363f98a3fe02db0cb0fb431b0e2dc24838a3cd95bb88916241b11 ./mutants/m6-tester-exempt.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m6-tester-exempt.rc
|
||||
72789ab63bd4aff24eac78a9a12b7db3c522675d4f6977104a258a2fe7c8fb5c ./mutants/m7-no-field-refuses-sender.out
|
||||
e386026671ece337244e6067683b1a408195797098adc02bb9c696e94fcfdf4b ./mutants/m7-no-field-refuses-sender.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m7-no-field-refuses-sender.rc
|
||||
4f2e17eac7a59a84c1eee14231bcd5ced50f983ec5491fe4e4ff95093b676d74 ./mutants/m8-no-field-refuses-target.out
|
||||
fcb265e09e1f6dcc2e3df019b20994e97c37eea55e05773e89fd486f7acee1f5 ./mutants/m8-no-field-refuses-target.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m8-no-field-refuses-target.rc
|
||||
1324349c8030f3137c07cfb82e635dc9b9a1071fd4c05e9ec935db726c603f74 ./mutants/m9-target-by-instance-name.out
|
||||
6c92efb6682803d80bea7c417d51e2a21c8770e582c14f188ab8f73059075ee9 ./mutants/m9-target-by-instance-name.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 ./mutants/m9-target-by-instance-name.rc
|
||||
f66b96e094e67bb18204ee486a084629be71433045a786c2388cae2d2ab4d487 ./mutants-run.txt
|
||||
32487046e90a4fb131541b2e3b5a0e8104fc66948f9305fe56c78eb01c05ae1a ./mutants.sh
|
||||
511bd9eb1f2c1331b9039398cbaa4d2415c5dbca33295f2fa81153850b0d8b4d ./row120.patch
|
||||
9f86e4b36ec077dfb2305b9f22be1947e88ec168acd1cf3f7015a652080d4fde ./suites/business.txt
|
||||
baeee568156e3a83e254d1faea8c9eb52aad723e28824b63cc2b8840437afb9e ./suites/bus.txt
|
||||
385125f144fac9af16f621712b7081d2156c8df0407011a62a00cee4b2c37e65 ./suites/cli.txt
|
||||
516344350d2d91143dc6dd143bbe11f4ecf77093a0adc125f9889822a034b091 ./suites/conversation.txt
|
||||
ff91d180b1ce2cb7ec4794d20b716de946b14815ca7410a8daa8b0c5e9c6151c ./suites/harness.txt
|
||||
4942038b6ef1427b1563b5b92e83c7b48f0c7e2b71563402879e775c7db01919 ./suites/seat.txt
|
||||
251402da8aa8cc8caae6bc5954702eb5cad6ef350eb89937b3d0f527d3543c41 ./suites.sh
|
||||
c51b022a499f0737c7a44f3333f468e146eaf800fe8f52e3342d1a1eff6961be ./suites/tasks.txt
|
||||
ccea37f4ffde1f160dcd1a1864988083a645d974e892065c956b338d0f8c853a ./suites/test-task.txt
|
||||
30270163980e5ef9dd02a4b9350ffc81eb287a9866563bb3d9857e085eb2ab21 ./suites/webui.txt
|
||||
@@ -0,0 +1,416 @@
|
||||
diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs
|
||||
index 4ab951135..54a4eb0d6 100644
|
||||
--- a/packages/bus/src/broker.mjs
|
||||
+++ b/packages/bus/src/broker.mjs
|
||||
@@ -147,6 +147,18 @@ export class Broker {
|
||||
}
|
||||
if (!Object.hasOwn(b.roles, b.arbiters.technical) || !Object.hasOwn(b.roles, b.arbiters.delivery))
|
||||
fail('invalid-arbiter');
|
||||
+ // Row 120 (#1614): a role definition's optional messaging lists. A malformed one refuses at boot.
|
||||
+ for (const d of Object.values(b.definitions ?? {})) {
|
||||
+ const m = d?.messaging;
|
||||
+ if (m === undefined || m === null) continue;
|
||||
+ if (
|
||||
+ !object(m) ||
|
||||
+ !['sendTo', 'receiveFrom'].every(
|
||||
+ (k) => Array.isArray(m[k]) && m[k].every((r) => id(r) && r !== 'tester'),
|
||||
+ )
|
||||
+ )
|
||||
+ fail('invalid-messaging');
|
||||
+ }
|
||||
}
|
||||
}
|
||||
#now() {
|
||||
@@ -311,6 +323,20 @@ export class Broker {
|
||||
? 'cross-role'
|
||||
: 'gated';
|
||||
}
|
||||
+ // Row 120 (#1614, REQ-ASSIST-2). A role definition may carry messaging { sendTo, receiveFrom },
|
||||
+ // naming the role definitions it may send to and receive from; "human" is the user. A definition
|
||||
+ // without the lists is unlimited, as before. The sender's lists are checked first, then the
|
||||
+ // target's, so a tester or the human sending to a limited role is refused unless listed.
|
||||
+ #checkMessaging(s, to) {
|
||||
+ const b = this.#business(s.business);
|
||||
+ const definition = (role) => b.definitions?.[b.roles[role]?.definition];
|
||||
+ const sender = s.human ? 'human' : s.tester ? 'tester' : b.roles[s.role]?.definition;
|
||||
+ const target = to === 'human' || to === 'tester' ? to : b.roles[to]?.definition;
|
||||
+ const out = s.human || s.tester ? undefined : definition(s.role)?.messaging;
|
||||
+ if (out && !out.sendTo.includes(target)) fail('message-recipient-refused');
|
||||
+ const inbound = to === 'human' || to === 'tester' ? undefined : definition(to)?.messaging;
|
||||
+ if (inbound && !inbound.receiveFrom.includes(sender)) fail('message-sender-refused');
|
||||
+ }
|
||||
#decision(s, id) {
|
||||
const d = this.#store.get('SELECT * FROM decisions WHERE id=? AND business=?', id, s.business);
|
||||
if (!d) fail('decision-not-found');
|
||||
@@ -940,6 +966,7 @@ export class Broker {
|
||||
if (a.to === 'tester') fail('unknown-role');
|
||||
}
|
||||
if (a.to !== 'human' && a.to !== 'tester' && !Object.hasOwn(b.roles, a.to)) fail('unknown-role');
|
||||
+ this.#checkMessaging(s, a.to);
|
||||
string(a.body, 32768);
|
||||
if (
|
||||
a.class !== undefined &&
|
||||
diff --git a/packages/bus/tests/messaging.test.mjs b/packages/bus/tests/messaging.test.mjs
|
||||
new file mode 100644
|
||||
index 000000000..def8d9b7e
|
||||
--- /dev/null
|
||||
+++ b/packages/bus/tests/messaging.test.mjs
|
||||
@@ -0,0 +1,202 @@
|
||||
+import test from 'node:test';
|
||||
+import assert from 'node:assert/strict';
|
||||
+import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
||||
+import { tmpdir } from 'node:os';
|
||||
+import { join } from 'node:path';
|
||||
+import { fileURLToPath } from 'node:url';
|
||||
+import { Store } from '../src/store.mjs';
|
||||
+import { Broker } from '../src/broker.mjs';
|
||||
+
|
||||
+// Row 120 (#1614, REQ-ASSIST-2): the broker limits who a role sends to and hears from.
|
||||
+// The assistant's lists are read from the shipped roles/assistant.json, so the test pins the file
|
||||
+// the stack runs, not a copy of it.
|
||||
+const shipped = (name) =>
|
||||
+ JSON.parse(readFileSync(fileURLToPath(new URL(`../../../roles/${name}.json`, import.meta.url)), 'utf8'));
|
||||
+const assistantLists = shipped('assistant').messaging;
|
||||
+
|
||||
+const within = { authority: { withinRole: ['message.send'], crossRole: [] } };
|
||||
+// Instance names are the business's; the lists name definitions.
|
||||
+const roles = {
|
||||
+ chief: { definition: 'chief-of-staff', ...within },
|
||||
+ assistant: { definition: 'assistant', ...within },
|
||||
+ pm: { definition: 'pm', ...within },
|
||||
+ coder: { definition: 'coder', ...within },
|
||||
+ scribe: { definition: 'scribe', ...within },
|
||||
+ warden: { definition: 'warden', ...within },
|
||||
+};
|
||||
+const definitions = {
|
||||
+ 'chief-of-staff': {},
|
||||
+ assistant: { messaging: assistantLists },
|
||||
+ pm: {},
|
||||
+ coder: {},
|
||||
+ // Only a send limit: tells a dropped sender-side check apart from the target-side check.
|
||||
+ scribe: { messaging: { sendTo: ['pm'], receiveFrom: ['human', 'pm', 'coder', 'warden', 'chief-of-staff'] } },
|
||||
+ // Only a receive limit: the same for the target side.
|
||||
+ warden: { messaging: { sendTo: ['human', 'pm', 'coder', 'scribe'], receiveFrom: ['pm'] } },
|
||||
+};
|
||||
+const demo = (extra = {}) => ({
|
||||
+ demo: {
|
||||
+ id: 'demo',
|
||||
+ human: 'jason',
|
||||
+ arbiters: { technical: 'pm', delivery: 'chief' },
|
||||
+ roles,
|
||||
+ definitions,
|
||||
+ vars: { 'bus.tester': true },
|
||||
+ ...extra,
|
||||
+ },
|
||||
+});
|
||||
+
|
||||
+function setup(t, businesses = demo()) {
|
||||
+ const root = mkdtempSync(join(tmpdir(), 'bus-messaging-'));
|
||||
+ const store = new Store(root);
|
||||
+ t.after(() => {
|
||||
+ store.close();
|
||||
+ rmSync(root, { recursive: true, force: true });
|
||||
+ });
|
||||
+ const b = new Broker({ store, businesses });
|
||||
+ const caps = {};
|
||||
+ for (const role of Object.keys(roles)) {
|
||||
+ caps[role] = b.bindLaunch({ business: 'demo', role, run: `${role}-run`, harness: 'pi' });
|
||||
+ b.request(caps[role], { verb: 'role.claim' });
|
||||
+ }
|
||||
+ caps.human = b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true });
|
||||
+ caps.tester = b.bindTester({ business: 'demo' });
|
||||
+ const call = (who, verb, args = {}) => b.request(caps[who], { verb, args });
|
||||
+ const send = (from, to, extra = {}) =>
|
||||
+ call(from, 'message.send', { to, body: from === 'tester' ? '[mos test] hello' : 'hello', ...extra });
|
||||
+ const count = (sql) => store.get(sql).n;
|
||||
+ const refusals = () =>
|
||||
+ store
|
||||
+ .all("SELECT actor_role, body FROM events WHERE kind='action.refused' ORDER BY seq")
|
||||
+ .map((e) => [e.actor_role, JSON.parse(e.body).code]);
|
||||
+ return { b, store, caps, call, send, count, refusals };
|
||||
+}
|
||||
+
|
||||
+const RECIPIENT = 'message-recipient-refused';
|
||||
+const SENDER = 'message-sender-refused';
|
||||
+
|
||||
+test('the assistant sends to the user and to the chief of staff only', (t) => {
|
||||
+ const { send, store } = setup(t);
|
||||
+ assert.deepEqual(assistantLists, { sendTo: ['human', 'chief-of-staff'], receiveFrom: ['human', 'chief-of-staff'] });
|
||||
+ for (const to of ['human', 'chief']) {
|
||||
+ const sent = send('assistant', to);
|
||||
+ const row = store.get('SELECT from_role, to_role FROM messages WHERE id=?', sent.id);
|
||||
+ assert.deepEqual({ ...row }, { from_role: 'assistant', to_role: to });
|
||||
+ }
|
||||
+});
|
||||
+
|
||||
+test('an assistant send to any other role, or to the tester, is refused and logged', (t) => {
|
||||
+ const { send, store, count, refusals } = setup(t);
|
||||
+ const asked = send('tester', 'pm', { class: 'REQUEST' });
|
||||
+ const messages = count('SELECT count(*) n FROM messages');
|
||||
+ const allowed = count("SELECT count(*) n FROM events WHERE kind='action.allowed'");
|
||||
+ const others = ['pm', 'coder', 'scribe', 'warden'];
|
||||
+ for (const to of others) assert.throws(() => send('assistant', to), { code: RECIPIENT }, to);
|
||||
+ // A reply to a real tester message is the only way past the tester gate; the list still refuses it.
|
||||
+ assert.throws(() => send('assistant', 'tester', { in_reply_to: asked.id }), { code: RECIPIENT }, 'tester');
|
||||
+ assert.deepEqual(
|
||||
+ refusals(),
|
||||
+ [...others, 'tester'].map(() => ['assistant', RECIPIENT]),
|
||||
+ );
|
||||
+ assert.equal(count('SELECT count(*) n FROM messages'), messages, 'no message stored');
|
||||
+ assert.equal(
|
||||
+ count("SELECT count(*) n FROM events WHERE kind='action.allowed'"),
|
||||
+ allowed,
|
||||
+ 'a refused send consumes no authority',
|
||||
+ );
|
||||
+ assert.equal(store.get("SELECT count(*) n FROM messages WHERE from_role='assistant'").n, 0);
|
||||
+});
|
||||
+
|
||||
+test('only the user and the chief of staff may send to the assistant; the tester is refused by default', (t) => {
|
||||
+ const { send, store, count, refusals } = setup(t);
|
||||
+ for (const from of ['human', 'chief']) {
|
||||
+ const sent = send(from, 'assistant');
|
||||
+ assert.equal(store.get('SELECT to_role FROM messages WHERE id=?', sent.id).to_role, 'assistant');
|
||||
+ }
|
||||
+ const messages = count('SELECT count(*) n FROM messages');
|
||||
+ // Senders without a list of their own, so only the assistant's receive list can refuse them.
|
||||
+ const others = ['pm', 'coder', 'tester'];
|
||||
+ for (const from of others) assert.throws(() => send(from, 'assistant'), { code: SENDER }, from);
|
||||
+ assert.deepEqual(refusals(), others.map((from) => [from, SENDER]));
|
||||
+ assert.equal(count('SELECT count(*) n FROM messages'), messages, 'no message stored');
|
||||
+ assert.equal(count("SELECT count(*) n FROM messages WHERE to_role='assistant' AND from_role='tester'"), 0);
|
||||
+});
|
||||
+
|
||||
+test('a sender-side list alone refuses a send to a role that has no list', (t) => {
|
||||
+ const { send, count, refusals } = setup(t);
|
||||
+ send('scribe', 'pm');
|
||||
+ assert.throws(() => send('scribe', 'coder'), { code: RECIPIENT });
|
||||
+ assert.throws(() => send('scribe', 'human'), { code: RECIPIENT });
|
||||
+ assert.deepEqual(refusals(), [['scribe', RECIPIENT], ['scribe', RECIPIENT]]);
|
||||
+ assert.equal(count("SELECT count(*) n FROM messages WHERE from_role='scribe'"), 1);
|
||||
+});
|
||||
+
|
||||
+test('a target-side list alone refuses a send from a role that has no list', (t) => {
|
||||
+ const { send, count, refusals } = setup(t);
|
||||
+ send('pm', 'warden');
|
||||
+ for (const from of ['coder', 'chief', 'human', 'tester'])
|
||||
+ assert.throws(() => send(from, 'warden'), { code: SENDER }, from);
|
||||
+ assert.deepEqual(refusals(), ['coder', 'chief', 'human', 'tester'].map((from) => [from === 'human' ? null : from, SENDER]));
|
||||
+ assert.equal(count("SELECT count(*) n FROM messages WHERE to_role='warden'"), 1);
|
||||
+});
|
||||
+
|
||||
+test('the sender list is checked before the target list', (t) => {
|
||||
+ const { send, refusals } = setup(t);
|
||||
+ // The scribe may not send to the warden, and the warden may not hear from the scribe: one code.
|
||||
+ assert.throws(() => send('scribe', 'warden'), { code: RECIPIENT });
|
||||
+ assert.deepEqual(refusals(), [['scribe', RECIPIENT]]);
|
||||
+});
|
||||
+
|
||||
+test('roles without the field behave exactly as before', (t) => {
|
||||
+ const { send, call, count, refusals } = setup(t);
|
||||
+ for (const [from, to] of [
|
||||
+ ['pm', 'coder'],
|
||||
+ ['coder', 'pm'],
|
||||
+ ['chief', 'pm'],
|
||||
+ ['pm', 'chief'],
|
||||
+ ['pm', 'human'],
|
||||
+ ['human', 'coder'],
|
||||
+ ['tester', 'pm'],
|
||||
+ ['tester', 'coder'],
|
||||
+ ])
|
||||
+ send(from, to);
|
||||
+ assert.deepEqual(refusals(), []);
|
||||
+ assert.equal(count('SELECT count(*) n FROM messages'), 8);
|
||||
+ // A role still answers a tester only in reply, whatever its lists.
|
||||
+ assert.throws(() => send('pm', 'tester'), { code: 'tester-reply-required' });
|
||||
+ const asked = send('tester', 'coder', { class: 'REQUEST' });
|
||||
+ send('coder', 'tester', { in_reply_to: asked.id });
|
||||
+ assert.equal(call('pm', 'message.receive').length > 0, true);
|
||||
+});
|
||||
+
|
||||
+test('a business with no role definitions at all is unlimited', (t) => {
|
||||
+ const businesses = demo();
|
||||
+ delete businesses.demo.definitions;
|
||||
+ const { send, refusals } = setup(t, businesses);
|
||||
+ for (const [from, to] of [['assistant', 'pm'], ['pm', 'assistant'], ['tester', 'assistant']]) send(from, to);
|
||||
+ assert.deepEqual(refusals(), []);
|
||||
+});
|
||||
+
|
||||
+test('a malformed messaging field refuses at boot', (t) => {
|
||||
+ const root = mkdtempSync(join(tmpdir(), 'bus-messaging-'));
|
||||
+ const store = new Store(root);
|
||||
+ t.after(() => {
|
||||
+ store.close();
|
||||
+ rmSync(root, { recursive: true, force: true });
|
||||
+ });
|
||||
+ const boot = (messaging) => () =>
|
||||
+ new Broker({ store, businesses: demo({ definitions: { ...definitions, pm: { messaging } } }) });
|
||||
+ const ok = { sendTo: [], receiveFrom: ['human'] };
|
||||
+ boot(ok)();
|
||||
+ for (const bad of [
|
||||
+ 'human',
|
||||
+ [],
|
||||
+ { sendTo: [] },
|
||||
+ { sendTo: 'human', receiveFrom: [] },
|
||||
+ { sendTo: ['tester'], receiveFrom: [] },
|
||||
+ { sendTo: [], receiveFrom: ['tester'] },
|
||||
+ { sendTo: [''], receiveFrom: [] },
|
||||
+ { sendTo: [7], receiveFrom: [] },
|
||||
+ ])
|
||||
+ assert.throws(boot(bad), { code: 'invalid-messaging' }, JSON.stringify(bad));
|
||||
+});
|
||||
diff --git a/packages/business/src/role.mjs b/packages/business/src/role.mjs
|
||||
index 37f99ce14..b990e8e12 100644
|
||||
--- a/packages/business/src/role.mjs
|
||||
+++ b/packages/business/src/role.mjs
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
|
||||
const CONTRACT_NAME = /^[a-z0-9][a-z0-9._-]{0,60}\.md$/;
|
||||
const V1_KEYS = ["roleVersion", "name", "tools", "network"];
|
||||
-const V2_KEYS = ["roleVersion", "name", "title", "contract", "tools", "network", "authority", "credentials"];
|
||||
+const V2_REQUIRED = ["roleVersion", "name", "title", "contract", "tools", "network", "authority", "credentials"];
|
||||
+const V2_KEYS = [...V2_REQUIRED, "messaging"];
|
||||
|
||||
function checkTools(tools) {
|
||||
if (!Array.isArray(tools) || tools.length === 0) refuse('role "tools" must be a non-empty array of tool names');
|
||||
@@ -45,6 +46,24 @@ function checkAuthority(authority) {
|
||||
return lists;
|
||||
}
|
||||
|
||||
+// REQ-ASSIST-2. Optional: a role with no "messaging" key may message anyone and be messaged by
|
||||
+// anyone the broker's other rules allow. With it, the broker refuses a send to a role outside
|
||||
+// sendTo and a send from a role outside receiveFrom. Entries name role definitions, since a role
|
||||
+// file can't know a business's instance names; "human" names the user. The tester is never a
|
||||
+// role, so it can't be listed.
|
||||
+function checkMessaging(messaging) {
|
||||
+ requireObject(messaging, 'role "messaging"');
|
||||
+ rejectUnknownKeys(messaging, ["sendTo", "receiveFrom"], 'role "messaging"');
|
||||
+ const lists = {};
|
||||
+ for (const key of ["sendTo", "receiveFrom"]) {
|
||||
+ lists[key] = requireDistinctList(messaging[key], `role messaging.${key}`, (entry) => {
|
||||
+ requireId(entry, `role messaging.${key} entry`);
|
||||
+ if (entry === "tester") refuse(`messaging.${key} lists tester, which is never a role`);
|
||||
+ });
|
||||
+ }
|
||||
+ return lists;
|
||||
+}
|
||||
+
|
||||
function checkGiteaScopes(scopes) {
|
||||
const list = requireDistinctList(scopes, "gitea scopes", (scope) => {
|
||||
const m = typeof scope === "string" ? /^(read|write):([a-z]+)$/.exec(scope) : null;
|
||||
@@ -103,7 +122,8 @@ function checkContract(contract, file) {
|
||||
|
||||
// Validate a parsed role document read from `file`. Returns a frozen role:
|
||||
// { roleVersion, name, title, contract, contractPath, tools, network,
|
||||
-// authority: { withinRole, crossRole }, credentials: [{ service, scopes }] }.
|
||||
+// authority: { withinRole, crossRole }, credentials: [{ service, scopes }],
|
||||
+// messaging: { sendTo, receiveFrom } | null }.
|
||||
export function validateRoleDocument(document, file) {
|
||||
requireObject(document, "role");
|
||||
if (document.roleVersion !== 1 && document.roleVersion !== 2) refuse('role "roleVersion" must be 1 or 2');
|
||||
@@ -116,10 +136,10 @@ export function validateRoleDocument(document, file) {
|
||||
const network = document.network === undefined ? "none" : checkNetwork(document.network);
|
||||
return deepFreeze({
|
||||
roleVersion: 1, name: document.name, title: null, contract: null, contractPath: null, tools, network,
|
||||
- authority: { withinRole: [], crossRole: [] }, credentials: [],
|
||||
+ authority: { withinRole: [], crossRole: [] }, credentials: [], messaging: null,
|
||||
});
|
||||
}
|
||||
- for (const key of V2_KEYS) {
|
||||
+ for (const key of V2_REQUIRED) {
|
||||
if (document[key] === undefined) refuse(`role version 2 requires "${key}"`);
|
||||
}
|
||||
return deepFreeze({
|
||||
@@ -132,6 +152,7 @@ export function validateRoleDocument(document, file) {
|
||||
network: checkNetwork(document.network),
|
||||
authority: checkAuthority(document.authority),
|
||||
credentials: checkCredentials(document.credentials),
|
||||
+ messaging: document.messaging === undefined ? null : checkMessaging(document.messaging),
|
||||
});
|
||||
}
|
||||
|
||||
diff --git a/packages/business/tests/assistant.test.mjs b/packages/business/tests/assistant.test.mjs
|
||||
index aaa809c44..ec421ef47 100644
|
||||
--- a/packages/business/tests/assistant.test.mjs
|
||||
+++ b/packages/business/tests/assistant.test.mjs
|
||||
@@ -14,6 +14,11 @@ const APPROVED = Object.freeze({
|
||||
network: "api-only",
|
||||
authority: Object.freeze({ withinRole: Object.freeze(["message.send"]), crossRole: Object.freeze([]) }),
|
||||
credentials: Object.freeze([]),
|
||||
+ // REQ-ASSIST-2: the user and the chief of staff, both ways. The broker enforces these lists (row 120).
|
||||
+ messaging: Object.freeze({
|
||||
+ sendTo: Object.freeze(["human", "chief-of-staff"]),
|
||||
+ receiveFrom: Object.freeze(["human", "chief-of-staff"]),
|
||||
+ }),
|
||||
});
|
||||
|
||||
const INSTANCE = "assistant";
|
||||
@@ -75,6 +80,16 @@ test("the assistant's authority is message.send within role and nothing else", (
|
||||
for (const action of ["task.create", "task.assign", "role.launch"]) assert.equal(classify(record, action), "gated", action);
|
||||
});
|
||||
|
||||
+test("the assistant talks to the user and the chief of staff only, and no other shipped role is limited", () => {
|
||||
+ const role = loadRole(REPO_ROLES, "assistant");
|
||||
+ assert.deepEqual(role.messaging, { sendTo: ["human", "chief-of-staff"], receiveFrom: ["human", "chief-of-staff"] });
|
||||
+ assert.ok(Object.isFrozen(role.messaging.sendTo));
|
||||
+ // Limiting another role is a policy change of its own; the shipped chief of staff and PM stay open.
|
||||
+ for (const name of ["chief-of-staff", "pm", "cto", "coder", "reviewer", "researcher"]) {
|
||||
+ assert.equal(loadRole(REPO_ROLES, name).messaging, null, name);
|
||||
+ }
|
||||
+});
|
||||
+
|
||||
test("a business grant through limits does not widen the role", () => {
|
||||
const record = resolved(loadFixture((doc) => {
|
||||
doc.roles[INSTANCE].vars = { "limits.authority": ["message.send", "task.create", "role.launch"] };
|
||||
diff --git a/packages/business/tests/role.test.mjs b/packages/business/tests/role.test.mjs
|
||||
index 150be7ec2..606a82636 100644
|
||||
--- a/packages/business/tests/role.test.mjs
|
||||
+++ b/packages/business/tests/role.test.mjs
|
||||
@@ -87,6 +87,7 @@ test("version 1 files keep loading with no authority", () => {
|
||||
assert.equal(role.roleVersion, 1);
|
||||
assert.equal(role.contractPath, null);
|
||||
assert.deepEqual(role.authority, { withinRole: [], crossRole: [] });
|
||||
+ assert.equal(role.messaging, null);
|
||||
assert.deepEqual(role.tools, ["read", "grep", "find", "ls", "bash"]);
|
||||
const dir = tmp();
|
||||
const file = writeJson(join(dir, "plain.json"), { roleVersion: 1, name: "plain", tools: ["read"] });
|
||||
@@ -131,6 +132,25 @@ test("authority: closed vocabulary, no gated-only action, no overlap", () => {
|
||||
refuses(() => check({ authority: { withinRole: [], crossRole: [], gated: [] } }), /unsupported role "authority" key/);
|
||||
});
|
||||
|
||||
+test("messaging: optional, role definitions and human, never the tester", () => {
|
||||
+ assert.equal(check({}).messaging, null);
|
||||
+ const role = check({ messaging: { sendTo: ["human", "chief-of-staff"], receiveFrom: [] } });
|
||||
+ assert.deepEqual(role.messaging, { sendTo: ["human", "chief-of-staff"], receiveFrom: [] });
|
||||
+ assert.ok(Object.isFrozen(role.messaging.sendTo));
|
||||
+ refuses(() => check({ messaging: "human" }), /role "messaging" must be a JSON object/);
|
||||
+ refuses(() => check({ messaging: { sendTo: [] } }), /messaging\.receiveFrom must be an array/);
|
||||
+ refuses(() => check({ messaging: { receiveFrom: [] } }), /messaging\.sendTo must be an array/);
|
||||
+ refuses(() => check({ messaging: { sendTo: [], receiveFrom: [], extra: [] } }), /unsupported role "messaging" key/);
|
||||
+ refuses(() => check({ messaging: { sendTo: ["pm", "pm"], receiveFrom: [] } }), /duplicate entry in role messaging\.sendTo/);
|
||||
+ refuses(() => check({ messaging: { sendTo: [], receiveFrom: ["not a role"] } }), /messaging\.receiveFrom entry/);
|
||||
+ refuses(() => check({ messaging: { sendTo: [7], receiveFrom: [] } }), /messaging\.sendTo entry/);
|
||||
+ refuses(() => check({ messaging: { sendTo: ["tester"], receiveFrom: [] } }), /messaging\.sendTo lists tester/);
|
||||
+ refuses(() => check({ messaging: { sendTo: [], receiveFrom: ["tester"] } }), /messaging\.receiveFrom lists tester/);
|
||||
+ // Version 1 has no messaging key.
|
||||
+ const file = writeJson(join(tmp(), "v1m.json"), { roleVersion: 1, name: "v1m", tools: ["read"], messaging: { sendTo: [], receiveFrom: [] } });
|
||||
+ refuses(() => loadRoleFile(file), /unsupported role key: "messaging"/);
|
||||
+});
|
||||
+
|
||||
test("credentials: Gitea scopes", () => {
|
||||
const gitea = (scopes) => check({ credentials: [{ service: "gitea", scopes }] });
|
||||
assert.deepEqual(gitea(["write:repository", "read:user"]).credentials[0].scopes, ["write:repository", "read:user"]);
|
||||
diff --git a/roles/assistant.json b/roles/assistant.json
|
||||
index 286b879f0..1208051d5 100644
|
||||
--- a/roles/assistant.json
|
||||
+++ b/roles/assistant.json
|
||||
@@ -9,5 +9,9 @@
|
||||
"withinRole": ["message.send"],
|
||||
"crossRole": []
|
||||
},
|
||||
- "credentials": []
|
||||
+ "credentials": [],
|
||||
+ "messaging": {
|
||||
+ "sendTo": ["human", "chief-of-staff"],
|
||||
+ "receiveFrom": ["human", "chief-of-staff"]
|
||||
+ }
|
||||
}
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 120 (#1614): the targeted gate (decision 136), run in the given worktree, every output teed to <outdir>.
|
||||
# Usage: suites.sh <worktree> <outdir>
|
||||
set -u -o pipefail
|
||||
WT=$(cd "$1" && pwd); OUT=$(mkdir -p "$2" && cd "$2" && pwd)
|
||||
cd "$WT" || exit 2
|
||||
node_suite() { # name dir
|
||||
setsid -w timeout -k 10 900 node --test "$2"/tests/*.test.mjs 2>&1 | tee "$OUT/$1.txt" >/dev/null
|
||||
echo "$1: rc=${PIPESTATUS[0]} $(grep -E '^ℹ (tests|pass|fail) ' "$OUT/$1.txt" | tr '\n' ' ')"
|
||||
}
|
||||
for d in business bus harness cli webui tasks conversation seat; do node_suite $d packages/$d; done
|
||||
setsid -w timeout -k 10 1200 bash scripts/test-task.sh 2>&1 | tee "$OUT/test-task.txt" >/dev/null
|
||||
echo "test-task: rc=${PIPESTATUS[0]} $(grep -E 'passed' "$OUT/test-task.txt" | tail -1)"
|
||||
echo done
|
||||
@@ -0,0 +1,173 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (33.018172ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (31.700437ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (48.974624ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (21.247823ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (26.752407ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (24.931388ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (28.059106ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (28.753582ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (147.24456ms)
|
||||
✔ agents: stranded is per role and per business (42.22657ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (17.749002ms)
|
||||
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (33.45022ms)
|
||||
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (35.012338ms)
|
||||
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (34.641582ms)
|
||||
✔ launch events require a human CLI capability; generic emit cannot forge authority events (22.226379ms)
|
||||
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (24.25032ms)
|
||||
✔ a recommendation that is not exactly one option key refuses invalid-recommendation after the options (35.118199ms)
|
||||
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (24.172124ms)
|
||||
✔ task action subjects and linked decision trail are complete and ordered (36.827595ms)
|
||||
✔ launch binding is durable and reconnecting requires the identical trusted record (17.293527ms)
|
||||
✔ business isolation includes inherited object names and cross-business message references (31.527562ms)
|
||||
✔ authority never transfers between action, run, target, unresolved or replaced role holder (46.867338ms)
|
||||
✔ task projection uses schema current view, skipping earlier and equal-start polls (27.861857ms)
|
||||
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (32.709455ms)
|
||||
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (20.24977ms)
|
||||
✔ both arbiters require human resolution when their cross-role route is themselves (32.411524ms)
|
||||
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (25.437031ms)
|
||||
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.648677ms)
|
||||
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (26.164423ms)
|
||||
✔ credentials refuses a role session and any argument (25.450923ms)
|
||||
✔ credentials reads the status on each request, and a broker without one lists none (24.458604ms)
|
||||
✔ the broker process answers credentials without a token, path or variable in its output, log or store (98.714704ms)
|
||||
✔ only validated broker references load; returned data and exceptions cannot expose a known token (5.697333ms)
|
||||
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (3.139342ms)
|
||||
✔ expiry refuses use and env references never become client data (1.008487ms)
|
||||
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (2.769815ms)
|
||||
✔ opaque tokens shorter than 16 characters refuse before use (0.597386ms)
|
||||
✔ a human resolve sends the raising role one message from the human, and its request is the answer (55.155801ms)
|
||||
✔ a cross-role decision resolved by its arbiter comes from that role and run, with no request (26.785051ms)
|
||||
✔ a human withdraw sends one message with the note (31.840319ms)
|
||||
✔ a human seen before the close leaves the request on the close's answer, not the seen (38.711116ms)
|
||||
✔ the raising run's withdraw, a supersede, a within-role raise and seen send nothing (42.521025ms)
|
||||
✔ refused closes send nothing: invalid-choice, decision-closed, not-resolver (40.552613ms)
|
||||
✔ a raising role nobody holds at the close gets the message on the next claim (35.701283ms)
|
||||
✔ a human DECISION send keeps its own instruction as the request, not a close's answer (34.82851ms)
|
||||
✔ roles lists open decisions routed to a role, and never the human inbox (42.52987ms)
|
||||
✔ recent lists closed decisions of every route, newest close first, with how each closed (38.150644ms)
|
||||
✔ recent gives the closing event as closed when the decision was seen first (36.456963ms)
|
||||
✔ recent stops at RECENT rows (134.457951ms)
|
||||
✔ every row is what trail already gives the same session for that id (58.602583ms)
|
||||
✔ a role session can't list decisions, so it can't read another role's open decisions (74.056209ms)
|
||||
✔ decisions is a read: one business, strict arguments, nothing written (50.624963ms)
|
||||
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (26.291467ms)
|
||||
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (17.799488ms)
|
||||
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (20.709103ms)
|
||||
✔ endLaunch leaves a claim another run took alone (25.717541ms)
|
||||
✔ refuse records action.refused against the caller with the code only (15.430986ms)
|
||||
✔ launches off refuses role.launch with launch-revoked until launches on (25.491186ms)
|
||||
✔ broker process: launch ops authorize role.launch, record refusals and end runs (127.879124ms)
|
||||
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (4.180153ms)
|
||||
✔ process reader gets own kernel identity without exposing environment values (0.792573ms)
|
||||
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.075175ms)
|
||||
✔ real pid 1 remains inspectable when its environment is protected (0.529502ms)
|
||||
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (37.459832ms)
|
||||
✔ missing and foreign-business citations refuse and roll back message and grant (25.038095ms)
|
||||
✔ cross-role sends still need a matching resolved decision and consume it once (35.918347ms)
|
||||
✔ messages pages all human messages newest first without writes; read state is history (466.127328ms)
|
||||
✔ messages refuses role callers, invalid paging and cursors outside this human feed (26.924423ms)
|
||||
✔ views messages passes only its paging arguments through (0.333235ms)
|
||||
✔ messages isolates businesses even with a valid foreign cursor (15.19104ms)
|
||||
✔ the assistant sends to the user and to the chief of staff only (43.459795ms)
|
||||
✔ an assistant send to any other role, or to the tester, is refused and logged (41.332066ms)
|
||||
✔ only the user and the chief of staff may send to the assistant; the tester is refused by default (39.641094ms)
|
||||
✔ a sender-side list alone refuses a send to a role that has no list (36.99997ms)
|
||||
✔ a target-side list alone refuses a send from a role that has no list (37.999199ms)
|
||||
✔ the sender list is checked before the target list (33.170239ms)
|
||||
✔ roles without the field behave exactly as before (63.034123ms)
|
||||
✔ a business with no role definitions at all is unlimited (47.396555ms)
|
||||
✔ a malformed messaging field refuses at boot (16.653703ms)
|
||||
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (96.752567ms)
|
||||
✔ startup token refusal returns safe code without value or partial listening broker (65.144963ms)
|
||||
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (92.687062ms)
|
||||
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (144.713997ms)
|
||||
✔ trusted host registers later launches; socket clients never have a registration verb (74.945846ms)
|
||||
✔ runtime excludes declared project roots even when host supplies no repoRoots (43.931453ms)
|
||||
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (57.567732ms)
|
||||
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (46.836662ms)
|
||||
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (25.085931ms)
|
||||
✔ a message with no reply gives an empty list (21.312843ms)
|
||||
✔ replies come oldest first and stop at REPLIES (109.858017ms)
|
||||
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (44.827884ms)
|
||||
✔ strict arguments; a role session gets human-required (62.405686ms)
|
||||
✔ views passes replies through as one call with the id (0.397755ms)
|
||||
✔ requirements returns the committed list to a holding role session, and no one else (32.382145ms)
|
||||
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (20.447891ms)
|
||||
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (22.126643ms)
|
||||
✔ a push is recorded as the action.allowed for git.push.working on the task, and only the host records one (36.27257ms)
|
||||
✔ review.request names the latest pushed commit and sends the reviewer a REVIEW-REQUEST (30.485028ms)
|
||||
✔ review.request says which run holds the reviewer role, or null, and a null request waits for a claim (38.496864ms)
|
||||
✔ review.request refuses a commit that isn't the latest push, a non-reviewer and a role without the action (51.053593ms)
|
||||
✔ reviewFetch answers only the addressed reviewer, for the request it cites (35.772469ms)
|
||||
✔ review.verdict answers one request once, to the role that asked (52.566248ms)
|
||||
✔ task.close on a pushed task needs an approve verdict for the latest push (64.057817ms)
|
||||
✔ a refusal the host decided is filed on the task it names, and only a task ref counts (30.385057ms)
|
||||
✔ v3b prototype refusals, views and append-only mutations (104.702739ms)
|
||||
✔ gated approval authorizes once, survives store reopen, and fresh approval works (39.642463ms)
|
||||
✔ another run cannot consume an approval; a failed check leaves it usable (28.526393ms)
|
||||
✔ two scheduled callers have exactly one grant and one consumed refusal (22.689644ms)
|
||||
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (45.879173ms)
|
||||
✔ class drift gated to cross-role refuses before consumption (33.372714ms)
|
||||
✔ class drift cross-role to gated refuses before consumption (44.695732ms)
|
||||
✔ class drift gated to within-role refuses before consumption (40.303413ms)
|
||||
✔ class drift cross-role to within-role refuses before consumption (38.377449ms)
|
||||
✔ class drift within-role to gated refuses before consumption (26.732121ms)
|
||||
✔ class drift within-role to cross-role refuses before consumption (29.50482ms)
|
||||
✔ message.send consumes approval and prevents a later send or authorize (26.9419ms)
|
||||
✔ role.revoke consumes approval and prevents a later revoke or authorize (27.824253ms)
|
||||
✔ a business that never switched launches reads allowed with no time and no event (24.978622ms)
|
||||
✔ launches revoked and then restored read allowed, with the restore time and event id (22.271336ms)
|
||||
✔ a task closed on the tracker leaves the open list, and total counts the open ones (21.204647ms)
|
||||
✔ a tombstone is not open (20.007994ms)
|
||||
✔ the open list stops at the cap and says so; total still counts every open task (29.186134ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (28.49025ms)
|
||||
✔ a human session reads the same state, with the same cap (30.84651ms)
|
||||
✔ reading state writes nothing: every table keeps its row count (25.284332ms)
|
||||
✔ creates private WAL store and excludes a second writer until explicit close (20.830814ms)
|
||||
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (24.909985ms)
|
||||
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (23.209544ms)
|
||||
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (88.335963ms)
|
||||
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (17.770709ms)
|
||||
✔ async transactions refuse before invoking their function (11.551344ms)
|
||||
✔ the session default fits under the reply cap and pages to the end with next (216.737892ms)
|
||||
✔ the short form: no description, assignees by role, every listed key (91.04615ms)
|
||||
✔ each filter, alone and together, and total counts every match (157.186363ms)
|
||||
✔ task_ref reads one task in full, as the whole board has it (108.622816ms)
|
||||
✔ no arguments is the read the Console and mosaic make, unchanged (82.669351ms)
|
||||
✔ an unknown argument or a bad value refuses invalid-request (332.934773ms)
|
||||
✔ tasks rows carry priority and requirement for every session that reads tasks (64.729979ms)
|
||||
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (73.721608ms)
|
||||
✔ an empty business and one with snapshots but no task.created read cleanly (16.265275ms)
|
||||
✔ recordTask keeps sync reads and a role write apart (35.028954ms)
|
||||
✔ read_at must be one canonical UTC format, so the projection compares strings safely (14.386426ms)
|
||||
✔ a bad entry refuses the whole record (14.83913ms)
|
||||
✔ taskView reads the projection for one business (17.374197ms)
|
||||
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (30.463452ms)
|
||||
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (277.119261ms)
|
||||
✔ without an adapter the server refuses every task verb (16.162486ms)
|
||||
✔ the runtime refuses an invalid adapter and closes a valid one (17.503514ms)
|
||||
✔ the process loads the S3 adapter from plain-data trackers (114.294212ms)
|
||||
✔ the tester binds only when the business sets bus.tester to exactly true (96.126382ms)
|
||||
✔ a business may not name a role tester, so the tester address is never a role (24.930271ms)
|
||||
✔ every verb outside the tester list refuses tester-refused and is recorded against the tester (42.958567ms)
|
||||
✔ a tester message is from tester, starts [mos test], and writes no human.input or authority (18.216883ms)
|
||||
✔ a role answers a tester only in reply to a tester message, and the answer never reaches the human (24.51164ms)
|
||||
✔ the tester reads the human views a reader reads (26.517098ms)
|
||||
✔ tester proof binds its own CLI, start time and nonce, refuses a managed run and allows an agent (1.350702ms)
|
||||
✔ neither CLI's nonce proves the other: each proof is bound to its own CLI and nonce variable (1.008321ms)
|
||||
✔ testerNonce reads only the tester's variable and gives null on any read error (0.246245ms)
|
||||
✔ an envelope carries exactly one of cap, human and tester; no tester authenticator refuses (16.183925ms)
|
||||
✔ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (674.940177ms)
|
||||
✔ socket capability stamps launch identity; shared views use wire, no SQL client (34.929356ms)
|
||||
✔ two wire claims serialize; a lost reply never automatically retries (24.362054ms)
|
||||
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (13.152774ms)
|
||||
✔ client preserves UTF-8 when a response divides a multibyte character (11.894657ms)
|
||||
✔ committed mutation followed by dropped reply reports unknown and is never retried (13.704895ms)
|
||||
ℹ tests 165
|
||||
ℹ suites 0
|
||||
ℹ pass 165
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1403.780689
|
||||
@@ -0,0 +1,88 @@
|
||||
✔ the role file loads and resolves through loadBusiness (9.018074ms)
|
||||
✔ the shipped file is exactly the approved list (1.075329ms)
|
||||
✔ the assistant's authority is message.send within role and nothing else (2.582336ms)
|
||||
✔ the assistant talks to the user and the chief of staff only, and no other shipped role is limited (1.138324ms)
|
||||
✔ a business grant through limits does not widen the role (2.224994ms)
|
||||
✔ a launch block whose by is the assistant is refused (2.321146ms)
|
||||
✔ the instance takes no credential references and no tracker (3.91734ms)
|
||||
✔ the contract passes work to the chief of staff and directs no one (0.780767ms)
|
||||
✔ config directory and file path follow MOSAIC_CONFIG (2.290712ms)
|
||||
✔ the fixture business validates and comes back frozen (6.287203ms)
|
||||
✔ two instances may share a definition (2.117529ms)
|
||||
✔ top-level refusals (6.141234ms)
|
||||
✔ arbiters and projects (9.692522ms)
|
||||
✔ role instances (3.752044ms)
|
||||
✔ Vikunja bots (10.352519ms)
|
||||
✔ a role without Vikunja takes no tracker block (2.567228ms)
|
||||
✔ credential references match the definition's services (2.923626ms)
|
||||
✔ launch (8.773364ms)
|
||||
✔ loadBusiness: file checks (2.084699ms)
|
||||
✔ loadBusiness: not a regular file (42.426391ms)
|
||||
✔ loading writes nothing (1.317938ms)
|
||||
✔ names that are Object.prototype properties don't count as declared (3.023732ms)
|
||||
✔ the shipped example refuses as written and validates once filled in (0.744414ms)
|
||||
✔ the role file loads and resolves through loadBusiness (7.181072ms)
|
||||
✔ the shipped file is exactly the approved list (0.216983ms)
|
||||
✔ the resolved record matches the approved list, so a widened file fails (1.698263ms)
|
||||
✔ the chief of staff holds role.launch and message.send; every other action is gated (1.612438ms)
|
||||
✔ the pm does not hold role.launch (1.67743ms)
|
||||
✔ a business grant of a gated action through limits does not widen the role (1.941551ms)
|
||||
✔ a limit that drops role.launch leaves the chief of staff with no launch block (2.074753ms)
|
||||
✔ a launch block whose by is the pm instance is refused (2.636124ms)
|
||||
✔ the contract names no duty to talk to the human and the pm contract no launch (0.471693ms)
|
||||
✔ the instance takes no credential references and no tracker (2.76813ms)
|
||||
✔ usage errors exit 4 (288.285205ms)
|
||||
✔ validate: a good business exits 0 and prints instance digests (72.349868ms)
|
||||
✔ validate: project files (349.295627ms)
|
||||
✔ validate: missing files and a broken system config (246.13448ms)
|
||||
✔ validate: credential reference problems exit 2 and name each one (65.704383ms)
|
||||
✔ validate: a token file inside the repository is refused (65.162002ms)
|
||||
✔ validate: role definitions come from MOSAIC_ROLES_DIR (192.946962ms)
|
||||
✔ resolve: prints one instance's record (267.766922ms)
|
||||
✔ resolve: refusals (390.370732ms)
|
||||
✔ parse: exactly one of file or env, plus the service's date (3.901125ms)
|
||||
✔ check: a good file has no problems (1.835461ms)
|
||||
✔ check never opens the file: a write-only token passes (0.561639ms)
|
||||
✔ check: file problems (1.650812ms)
|
||||
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (1.471809ms)
|
||||
✔ check: dates and environment references (0.690788ms)
|
||||
✔ path and load (3.024475ms)
|
||||
✔ refusals (1.783692ms)
|
||||
✔ systemVars flattens the validated config (2.504468ms)
|
||||
✔ precedence: system, business, project, project role, agent (6.682658ms)
|
||||
✔ limits narrow the definition and never widen it (3.523077ms)
|
||||
✔ role.launch stays within-role only for the instance the launch block names (8.403471ms)
|
||||
✔ limits.authority without role.launch leaves the launcher with no launch block (2.445734ms)
|
||||
✔ limits.authority narrows cross-role actions too (1.40432ms)
|
||||
✔ classify (1.255286ms)
|
||||
✔ the record carries what the broker and launcher need (1.173596ms)
|
||||
✔ digest: key order doesn't matter, any value change does (7.56278ms)
|
||||
✔ refusals (2.551652ms)
|
||||
✔ the four shipped version 2 roles load (4.300439ms)
|
||||
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.313518ms)
|
||||
✔ shipped authority follows the note's table (0.816291ms)
|
||||
✔ every role file in roles/ loads through the validator, and a v2 contract exists (0.945573ms)
|
||||
✔ version 1 files keep loading with no authority (1.238984ms)
|
||||
✔ the conductor policy isn't a role (0.205618ms)
|
||||
✔ a missing role file is exit 4, a symbolic link too (0.47218ms)
|
||||
✔ version 2 refusals (1.391278ms)
|
||||
✔ authority: closed vocabulary, no gated-only action, no overlap (2.315443ms)
|
||||
✔ messaging: optional, role definitions and human, never the tester (1.602377ms)
|
||||
✔ credentials: Gitea scopes (0.790311ms)
|
||||
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.475666ms)
|
||||
✔ credentials: services (0.794478ms)
|
||||
✔ contract: a non-empty regular Markdown file beside the role file (0.720032ms)
|
||||
✔ every key names known layers and a merge rule (1.201689ms)
|
||||
✔ unknown keys and wrong layers refuse (1.052112ms)
|
||||
✔ types (2.473527ms)
|
||||
✔ merge: defaults, then the most specific layer wins (0.427686ms)
|
||||
✔ merge: limits only narrow, and provenance lists each source (0.569149ms)
|
||||
✔ merge doesn't change its inputs (0.263011ms)
|
||||
ℹ tests 80
|
||||
ℹ suites 0
|
||||
ℹ pass 80
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 2020.969416
|
||||
@@ -0,0 +1,201 @@
|
||||
✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (2116.411917ms)
|
||||
✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (125.041866ms)
|
||||
✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (260.670531ms)
|
||||
✔ NO_COLOR: no escape codes, except reverse video on the selected row (393.684622ms)
|
||||
✔ 1-9 shows the decide flow's own lines and question; only y resolves (83.046938ms)
|
||||
✔ the app shows the same decide text as mosaic decide (29.459716ms)
|
||||
✔ any key but y or Y at the question answers No; nothing is sent (286.245324ms)
|
||||
✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (110.741056ms)
|
||||
✔ under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10) (455.440909ms)
|
||||
✔ a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12) (57.536474ms)
|
||||
✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (32.781937ms)
|
||||
✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (478.172212ms)
|
||||
✔ quitting at the question answers No and sends nothing, armed or not (63.769302ms)
|
||||
✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (75.355971ms)
|
||||
✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (86.605575ms)
|
||||
✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (431.114966ms)
|
||||
✔ at every size the decide box shows each line y depends on, or says it is hidden (9884.060969ms)
|
||||
✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (68.033548ms)
|
||||
✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (30.703387ms)
|
||||
✔ help or a detail box cut to fit keeps its last line, Esc included (Dewey) (16.461242ms)
|
||||
✔ keys: views, moving, trail of a selection, the command line, help and quit (75.540115ms)
|
||||
✔ one option: the status line says Press 1, not 1-1 (19.712525ms)
|
||||
✔ no business: the app says so and :business picks one (9.418059ms)
|
||||
✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (1.875933ms)
|
||||
✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (177.927723ms)
|
||||
✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.504322ms)
|
||||
✔ decide with --note still asks; a No sends nothing (10.204586ms)
|
||||
✔ the app's business comes from a live bus host only (1.196595ms)
|
||||
✔ runApp leaves the terminal as it found it on q, end of input, SIGTERM, SIGINT, SIGHUP and an error, the first frame's included (7.275953ms)
|
||||
✔ draw is pure: same state, same frame (0.860564ms)
|
||||
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (39.005329ms)
|
||||
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (30.307267ms)
|
||||
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (22.670088ms)
|
||||
✔ decide prints a declining choice as declining (19.988508ms)
|
||||
✔ an unknown outcome is reported once and never resent (22.848019ms)
|
||||
✔ a decision closed before the answer arrives exits 2 and points at its trail (19.489389ms)
|
||||
✔ a prefix that matches two open decisions exits 2 and resolves neither (19.341497ms)
|
||||
✔ without --business a command uses the live host's business, and a stale host.json is not a host (15.584132ms)
|
||||
✔ every human command refuses inside an agent run before it touches the bus (15.904131ms)
|
||||
✔ usage errors exit 4; no business and no host is a usage error (16.156684ms)
|
||||
✔ agents and tasks print through the broker (15.959341ms)
|
||||
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.570445ms)
|
||||
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (80.201337ms)
|
||||
✔ trackers come from the tracker.* variables of the one project that names a tracker project (132.03746ms)
|
||||
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (54.997926ms)
|
||||
✔ two projects that each name a tracker project refuse, since the boot shape holds one (51.258077ms)
|
||||
✔ a business without tracker.baseUrl gets no trackers entry (62.568593ms)
|
||||
✔ an unknown business and a broken system config refuse with exit 3 (78.02683ms)
|
||||
✔ follow prints an unsolicited message after plain talk returned (53.816156ms)
|
||||
✔ talk --follow continues after reply and prints a later unsolicited message (44.004268ms)
|
||||
✔ inbox lists all unread human messages under decisions, does not deliver or read them (355.994745ms)
|
||||
✔ follow refuses agent context and bad arguments (10.262534ms)
|
||||
✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (13.468838ms)
|
||||
✔ plain talk leaves the process SIGINT handlers unchanged (12.145606ms)
|
||||
✔ empty views say so (1.155634ms)
|
||||
✔ the trail keeps the broker's order and names a decision's task without its rows (1.463924ms)
|
||||
✔ tasks print the tracker fields the snapshot carries (0.246609ms)
|
||||
✔ the review loop: the coder pushes and asks, the reviewer fetches and approves, and the task's trail holds each step (570.496326ms)
|
||||
✔ refusals: no action, non-fast-forward, an unknown commit or request, and a push for another task moves only that task's ref (401.023112ms)
|
||||
✔ the size cap refuses a push before any byte (297.302987ms)
|
||||
✔ a fetch over the cap refuses before any byte reaches the reviewer (544.912812ms)
|
||||
✔ the socket refuses a request with no capability, an unknown one, and a bad request line (237.782023ms)
|
||||
✔ a rigged clone runs nothing: hooks, fsmonitor, a filter, a gitlink and a symlinked .git/config leave no marker (441.642002ms)
|
||||
✔ an identity with no role, or of another business, refuses not-a-role and moves no ref (304.113419ms)
|
||||
✔ a review of a project the business no longer has refuses unknown-project (304.422315ms)
|
||||
✔ past maxActive connections the socket refuses git-busy, and a hung-up connection frees its slot (187.072605ms)
|
||||
✔ close() answers host-closing to a connection still sending its request line (214.672439ms)
|
||||
✔ a bundle that trickles in refuses body-timeout at its deadline and frees its slot (656.543337ms)
|
||||
✔ a reviewer gone before its bundle was sent is filed fetch-aborted, not as the host's fault (663.683325ms)
|
||||
✔ a reviewer that hangs up the moment it has the whole bundle was served, not aborted (673.620838ms)
|
||||
✔ send: a close after every byte left is served; one that fails a queued write is fetch-aborted (43.315798ms)
|
||||
✔ a bundle with no bytes for bodyIdle refuses body-timeout long before its deadline (484.624556ms)
|
||||
✔ send: a close once the loop is past its last chunk is served, and a failed write there is fetch-aborted (45.361863ms)
|
||||
✔ letGo waits for an answer to leave for at most its cap, then destroys the socket (301.296985ms)
|
||||
✔ a record that fails and can't be undone refuses rollback-failed, which the host logs (374.693898ms)
|
||||
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (983.91634ms)
|
||||
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (266.381755ms)
|
||||
✔ a second host for the same data root refuses with exit 3 while the first runs (156.832536ms)
|
||||
✔ two bus starts racing for one data root leave one host running; the other refuses that a host already runs (666.402491ms)
|
||||
✔ a host takes over a host.lock its dead host left, and puts back a live claim that replaced it meanwhile (2.635772ms)
|
||||
✔ a host.lock that goes between the check and the read is tried again; one that can't be parsed still refuses as unreadable (1.645445ms)
|
||||
✔ a live host's lock stays, however old the file is (0.967894ms)
|
||||
✔ a host.lock left by a host killed with SIGKILL doesn't stop the next bus start (989.338016ms)
|
||||
✔ while a host boots, a reader sees no host: the human commands still ask for --business, stop and status find none (322.474013ms)
|
||||
✔ a host started before host.lock existed, holding only host.json, still refuses a second start, which leaves no lock (50.78441ms)
|
||||
✔ a broker fork that throws leaves no host.lock and no host.json (47.437672ms)
|
||||
✔ a broker send that throws leaves no host.lock and no host.json (64.733604ms)
|
||||
✔ a broker that misses its boot wait leaves no host.lock and no host.json (75.996801ms)
|
||||
✔ a notifier fork that throws stops the broker and leaves no host.lock and no host.json (128.935076ms)
|
||||
✔ a notifier send that throws stops the broker and leaves no host.lock and no host.json (128.361029ms)
|
||||
✔ a boot reply with no reader for the business refuses with exit 3 naming it, before the notifier starts, and leaves no host.lock and no live broker (116.81384ms)
|
||||
✔ a boot reply with no reader for the business refuses with exit 3 naming it, with no notifier, and leaves no host.lock and no live broker (125.106622ms)
|
||||
✔ a host.json that can't be written stops both children and leaves no host.lock (328.099308ms)
|
||||
✔ an error event on the broker before it replies fails the start at once, and leaves no host.lock and no live child (66.58687ms)
|
||||
✔ an error event on the notifier before it replies fails the start at once, and leaves no host.lock and no live child (147.13617ms)
|
||||
✔ an error event on the broker while the notifier starts fails the start once the notifier replies (250.46078ms)
|
||||
✔ an error event on the broker once the host is up stops the host with exit 1, and leaves no host.lock and no host.json (264.447458ms)
|
||||
✔ an error event on the notifier once the host is up stops the host with exit 1, and leaves no host.lock and no host.json (261.248466ms)
|
||||
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1117.621663ms)
|
||||
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (185.809604ms)
|
||||
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (91.788126ms)
|
||||
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (165.133814ms)
|
||||
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (167.407504ms)
|
||||
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (95.358177ms)
|
||||
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (153.511536ms)
|
||||
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (109.914511ms)
|
||||
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (189.602837ms)
|
||||
✔ watchChildren reports a child that died before it was called, and one that dies later (27.531598ms)
|
||||
✔ bus stop refuses to signal a live pid that is not a bus host (203.401817ms)
|
||||
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (288.573632ms)
|
||||
✔ bus start refuses with exit 3 without a notifier config (131.367558ms)
|
||||
✔ bus start runs until bus stop; status reports it while it runs (756.806ms)
|
||||
✔ bus-service.sh renders the unit and installs it into a given directory (69.346682ms)
|
||||
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (87.59192ms)
|
||||
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (17.676233ms)
|
||||
✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (11.588406ms)
|
||||
✔ bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest (1319.321052ms)
|
||||
✔ the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code (643.207922ms)
|
||||
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (3793.868669ms)
|
||||
✔ a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left (132.059ms)
|
||||
✔ a runner that stops at once ends its launch with the runner's reason (207.561032ms)
|
||||
✔ a Claude Code launch with no session credential refuses before it starts (139.033826ms)
|
||||
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (221.645576ms)
|
||||
✔ a runner killed from the host ends its launch as killed, not failed (252.541668ms)
|
||||
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (115.191945ms)
|
||||
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1324.444421ms)
|
||||
✔ 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 (667.81756ms)
|
||||
✔ 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 (3615.885662ms)
|
||||
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (88.802705ms)
|
||||
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (97.747241ms)
|
||||
✔ a launch client that never closes its side doesn't hold the host's close (103.27127ms)
|
||||
✔ a runner that ignores SIGTERM is killed when the host closes (1222.972891ms)
|
||||
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (5.187645ms)
|
||||
✔ zoned uses the IANA zone across DST (27.702014ms)
|
||||
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (51.868019ms)
|
||||
✔ two blocking decisions get two DMs with different nonces (23.618183ms)
|
||||
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.551569ms)
|
||||
✔ a failed DM is journaled, backs off, and is retried until it lands (27.017461ms)
|
||||
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (29.717272ms)
|
||||
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (29.275162ms)
|
||||
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (20.721551ms)
|
||||
✔ 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 (146.29667ms)
|
||||
✔ a restart after the second refusal does not send before that refusal's 30 min are up (55.706245ms)
|
||||
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (15.153107ms)
|
||||
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (11.632718ms)
|
||||
✔ an inbox read failure is logged and the next poll retries (0.96808ms)
|
||||
✔ no Discord id reaches the journal or the log (12.009976ms)
|
||||
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (3.390838ms)
|
||||
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.034848ms)
|
||||
✔ the journal: a whole file that is one torn line truncates to empty (1.590129ms)
|
||||
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.695344ms)
|
||||
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.711994ms)
|
||||
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.613363ms)
|
||||
✔ the journal: a symlinked directory refuses and says it is a link (0.393952ms)
|
||||
✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (2.292983ms)
|
||||
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.578418ms)
|
||||
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.617417ms)
|
||||
✔ digest content stays within Discord's 2000 characters (0.351395ms)
|
||||
✔ runLoop never overlaps ticks and stops after the one in flight (110.388253ms)
|
||||
✔ a human command refused with human-required prints the other-tree line and exits 3 (3.735127ms)
|
||||
✔ a --tester command refused with tester-required prints the other-tree line and exits 3 (1.87476ms)
|
||||
✔ other exit-3 refusals print no other-tree line (2.284622ms)
|
||||
✔ the line names no path (0.2046ms)
|
||||
✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (788.013264ms)
|
||||
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (682.506446ms)
|
||||
✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (697.84633ms)
|
||||
✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (411.97049ms)
|
||||
✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (4613.202521ms)
|
||||
✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (598.712143ms)
|
||||
✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (174.897206ms)
|
||||
✔ install never shows a template name on systemd 261; template drop-ins still refuse (186.717041ms)
|
||||
✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (165.047911ms)
|
||||
✔ inherited mosaic-.service.d override refuses without changing the unit (199.519586ms)
|
||||
✔ inherited service.d override refuses without changing the unit (203.415349ms)
|
||||
✔ inherited drop-in unreadable directory or file refuses before writing (150.440788ms)
|
||||
✔ talk --tester sends a [mos test] message as tester from inside an agent and prints the role's reply (39.682822ms)
|
||||
✔ talk --tester refuses text without the [mos test] prefix before any call (18.182238ms)
|
||||
✔ the read verbs take --tester inside an agent, and the trail shows the tester as the sender (27.528075ms)
|
||||
✔ decide, stop and launches take no --tester (19.49056ms)
|
||||
✔ the tester transport runs tester-cli.mjs, and the tester's refusals exit 3 (0.555408ms)
|
||||
✔ with no stand-in transport, --tester runs tester-cli.mjs on the data root's socket (1.029717ms)
|
||||
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 (365.68751ms)
|
||||
✔ the transport writes {business, verb, args} to the child and reads its JSON (60.374616ms)
|
||||
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2263.973176ms)
|
||||
✔ busExit and refuseInsideAgent (0.964095ms)
|
||||
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (63.564983ms)
|
||||
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1036.007604ms)
|
||||
✔ a late reply the Console has listed still reaches the next talk (2041.249387ms)
|
||||
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (18.43057ms)
|
||||
✔ launches off and on go to the broker and change the business's launch state (16.930524ms)
|
||||
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (16.00052ms)
|
||||
ℹ tests 191
|
||||
ℹ suites 0
|
||||
ℹ pass 191
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 15562.129506
|
||||
@@ -0,0 +1,190 @@
|
||||
✔ W1: two processes acquire the same pair at once; exactly one claim (147.314124ms)
|
||||
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (8.169187ms)
|
||||
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (4.973806ms)
|
||||
✔ W2: acquire while a claim is reserved or active refuses already-active (136.133248ms)
|
||||
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (211.202132ms)
|
||||
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (178.70552ms)
|
||||
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (20.31256ms)
|
||||
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (18.004249ms)
|
||||
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (3.239547ms)
|
||||
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (5909.421347ms)
|
||||
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (24845.03757ms)
|
||||
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (151.469683ms)
|
||||
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (98.292251ms)
|
||||
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (272.179518ms)
|
||||
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (196.814744ms)
|
||||
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (223.432208ms)
|
||||
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (33.903418ms)
|
||||
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (108.818575ms)
|
||||
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (34.379195ms)
|
||||
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (97.664158ms)
|
||||
✔ W11: the controller writes no session file; only the fake engine's own appends appear (23.638195ms)
|
||||
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (52.55234ms)
|
||||
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (50.084663ms)
|
||||
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.869492ms)
|
||||
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.238263ms)
|
||||
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.993733ms)
|
||||
✔ no shim from this file's tests is left running for the after hook (6.085784ms)
|
||||
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (326.228134ms)
|
||||
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (184.676408ms)
|
||||
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (313.031586ms)
|
||||
✔ K4: two engines; force stop one; the other survives by independent observation (2346.333994ms)
|
||||
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (176.797833ms)
|
||||
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (295.103557ms)
|
||||
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (192.813929ms)
|
||||
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4430.504157ms)
|
||||
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (455.386665ms)
|
||||
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (360.197047ms)
|
||||
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (286.101868ms)
|
||||
✔ K6: recover without proof, without confirmation, or with changed pins is refused (72.722874ms)
|
||||
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (32.211902ms)
|
||||
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (41.964345ms)
|
||||
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3031.547882ms)
|
||||
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (5.844001ms)
|
||||
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (31.325106ms)
|
||||
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (26.89116ms)
|
||||
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (88.033228ms)
|
||||
✔ R1: after a proven force stop the controller releases the scope; the shim and the unit are gone (187.926163ms)
|
||||
✔ R2: close releases the scope of a binding proven stopped, once, when the stop's own release hasn't run (298.337515ms)
|
||||
✔ R3: no release after an unavailable stop or an unverified proof: the scope and its shim stay as evidence (703.115156ms)
|
||||
✔ E1: a natural engine exit with no member left: an engine-exit stop, stopped on a proof naming the engine, and the scope released (214.726372ms)
|
||||
✔ E2: an engine exit with a tool child alive: the stop ends uncertain and nothing is released; a force stop then ends it (2252.994853ms)
|
||||
✔ E3: an engine exit and a force stop take one escalation slot, in either order (648.311458ms)
|
||||
✔ E4: a shim that doesn't answer: the engine-exit stop misses its deadline, ends uncertain and frees the slot (713.225069ms)
|
||||
✔ E5: an engine exit after a force stop ended uncertain starts no engine-exit stop; the force stop stays current (2437.193775ms)
|
||||
✔ E6: the engine-exit observation refuses a reaped process that isn't the recorded engine: another PID or start ticks (190.701737ms)
|
||||
✔ R5: the harness finds a running shim and reaps it, and never signals a shim outside a mosaic-chat- scope (Filbert N1 and N2 on #1533) (378.980285ms)
|
||||
✔ R6: releaseCohort releases nothing while the engine runs, for another invocation ID, or for a cohort without a scope (124.182458ms)
|
||||
✔ R7: controller killed after the claim is stopped, before the release: the restart releases the scope (2338.522488ms)
|
||||
✔ R8: controller killed between the two keys' stopped revisions: the restart releases the scope (248.409137ms)
|
||||
✔ R9: a release that ended unavailable is tried again on a confirmed recover, not a refused one (226.277464ms)
|
||||
✔ R10: close releases and signals nothing for a stopped binding whose proof no longer verifies (Filbert N3 on #1536) (182.005224ms)
|
||||
✔ R11: close({ killEngine: true }) after a proven stop sends no signal to the recorded engine PID; a restart releases nothing more (2280.103732ms)
|
||||
✔ R12: a restart onto a claim stopped on a boot proof releases nothing, though its scope is listed (182.913047ms)
|
||||
✔ no shim from this file's tests is left running for the after hook (5.944422ms)
|
||||
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (46.673749ms)
|
||||
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (31.620248ms)
|
||||
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (25.922721ms)
|
||||
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (36.098628ms)
|
||||
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (20.520817ms)
|
||||
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.822662ms)
|
||||
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (20.675984ms)
|
||||
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (125.903763ms)
|
||||
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (37.196412ms)
|
||||
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (20.335031ms)
|
||||
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (37.832584ms)
|
||||
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (35.009043ms)
|
||||
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.323894ms)
|
||||
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (32.087678ms)
|
||||
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (46.332174ms)
|
||||
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (39.943669ms)
|
||||
✔ terminal: engine control characters are made visible; a lost connection refuses submit (24.386121ms)
|
||||
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.754873ms)
|
||||
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.788311ms)
|
||||
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.602081ms)
|
||||
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.739317ms)
|
||||
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.424058ms)
|
||||
✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.843673ms)
|
||||
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (6.079769ms)
|
||||
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.611154ms)
|
||||
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.482935ms)
|
||||
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.135434ms)
|
||||
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (324.842274ms)
|
||||
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (58.92724ms)
|
||||
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (81.298244ms)
|
||||
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (139.149165ms)
|
||||
✔ H4: self-takeover is refused (17.124571ms)
|
||||
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (99.989269ms)
|
||||
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (99.625155ms)
|
||||
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (24.315053ms)
|
||||
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (83.717162ms)
|
||||
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (135.676924ms)
|
||||
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (20.388562ms)
|
||||
✔ H13: a retry with the same request ID and different text is refused (18.216305ms)
|
||||
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (141.875874ms)
|
||||
✔ H15: a revoked connection's command is refused; the revocation fence holds (71.25178ms)
|
||||
✔ H16: a second controller for the same session refuses already-active; the first is untouched (20.340538ms)
|
||||
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (66.087564ms)
|
||||
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (25.756544ms)
|
||||
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (58.438373ms)
|
||||
✔ H18: two prompts before any native output: the second refuses busy; one engine write (15.643318ms)
|
||||
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (121.407664ms)
|
||||
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.881168ms)
|
||||
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (474.537487ms)
|
||||
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (373.526082ms)
|
||||
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (338.523382ms)
|
||||
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2404.172484ms)
|
||||
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (464.81979ms)
|
||||
✔ no shim from this file's tests is left running for the after hook (5.483997ms)
|
||||
✔ a plain conversation: catalogue row, one page, CHAT-01 records (7.59437ms)
|
||||
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (2.244514ms)
|
||||
✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.170991ms)
|
||||
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (3.072478ms)
|
||||
✔ F1: an unreadable fork is never merged into another branch's history (2.740145ms)
|
||||
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (5.331747ms)
|
||||
✔ F1: a file whose entries are all unreadable shows a notice per line (1.722364ms)
|
||||
✔ F2: a truncated trailing line marks the view incomplete, not an error (2.062629ms)
|
||||
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (4.210193ms)
|
||||
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (5.499166ms)
|
||||
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (7.492917ms)
|
||||
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (6.521299ms)
|
||||
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (12.950365ms)
|
||||
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (15.95767ms)
|
||||
✔ F8: a file swapped for a symlink after the catalogue is refused (3.028829ms)
|
||||
✔ F9: registrations never add or redirect a root (2.021271ms)
|
||||
✔ F10: a header cwd naming another project is refused (4.900495ms)
|
||||
✔ F11: parentSession renders with a marker and the parent is never opened (0.980309ms)
|
||||
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (5.905018ms)
|
||||
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.937872ms)
|
||||
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.446615ms)
|
||||
✔ F13: compaction is a marker in place, then the retained content (0.839393ms)
|
||||
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (628.733969ms)
|
||||
✔ fragments never cut a surrogate pair and keep an empty string (5.451232ms)
|
||||
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.725356ms)
|
||||
✔ unknown conversations, empty files and non-Pi files refuse (2.708436ms)
|
||||
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.312487ms)
|
||||
✔ a seat directory without search permission refuses that root, not the catalogue (2.243135ms)
|
||||
✔ every page and cursor is a valid CHAT-01 record (861.402855ms)
|
||||
✔ the engine pin holds for the installed package (2.305156ms)
|
||||
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (337.910614ms)
|
||||
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (665.490366ms)
|
||||
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (279.533657ms)
|
||||
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (95.539877ms)
|
||||
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (51.388007ms)
|
||||
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (25.719496ms)
|
||||
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (20.932096ms)
|
||||
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (30.184421ms)
|
||||
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (23.272921ms)
|
||||
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (118.821664ms)
|
||||
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (55.559963ms)
|
||||
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1534.850576ms)
|
||||
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (15.34435ms)
|
||||
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (70.856514ms)
|
||||
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (14.854192ms)
|
||||
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (16.029904ms)
|
||||
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (33.682146ms)
|
||||
✔ N10: fake conformance (32.569765ms)
|
||||
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (32.121098ms)
|
||||
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (20.555326ms)
|
||||
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (65.41769ms)
|
||||
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (28.467046ms)
|
||||
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (29.604154ms)
|
||||
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (23.672948ms)
|
||||
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (13.474106ms)
|
||||
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (27.731744ms)
|
||||
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (108.676482ms)
|
||||
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (136.310368ms)
|
||||
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (84.960867ms)
|
||||
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (15.496162ms)
|
||||
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (15.293063ms)
|
||||
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (15.88726ms)
|
||||
✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (39.239883ms)
|
||||
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (29.400749ms)
|
||||
ℹ tests 182
|
||||
ℹ suites 0
|
||||
ℹ pass 182
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 34698.863517
|
||||
@@ -0,0 +1,92 @@
|
||||
✔ sessionModel: agent vars win, then the system's execution settings (14.614198ms)
|
||||
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (8.183231ms)
|
||||
✔ a chief-of-staff bundle lists launch among the bus tools and the manifest's typed tools (3.9943ms)
|
||||
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (4.050297ms)
|
||||
✔ the prompt's built-in tools line is the harness's own names, as the manifest lists them (11.980502ms)
|
||||
✔ the prompt names the project clones in the workspace, and none when there are none (7.515601ms)
|
||||
✔ the prompt explains the review loop to a role with push_working, and reviews to one with fetch_review (9.605105ms)
|
||||
✔ a bundle is written once: an existing file refuses (1.948565ms)
|
||||
✔ a path with a single quote can't go into the hook command (2.273895ms)
|
||||
✔ allow exits 0, a deny exits 2 with the reason on stderr (138.772682ms)
|
||||
✔ a missing or wrong policy, or a bad event, exits 2 (101.906662ms)
|
||||
✔ the bundle's wrapped command: a missing gate or node still blocks (1119.718996ms)
|
||||
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (870.320284ms)
|
||||
✔ claude: the hook alone blocks a path outside the workspace (529.857591ms)
|
||||
✔ claude: a second turn resumes the first turn's session (815.01276ms)
|
||||
✔ claude adapter: --restricted is always passed (6.251245ms)
|
||||
✔ claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable (21.484195ms)
|
||||
✔ claude adapter: a directory pin exits 2 before it runs; a pin with a space runs (7.844731ms)
|
||||
✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (768.379655ms)
|
||||
✔ claude: a missing hook or MCP file refuses before claude starts (8.453723ms)
|
||||
✔ pi: policy tools and typed tools pass, anything else is blocked (4.64508ms)
|
||||
✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.953931ms)
|
||||
✔ file tool paths must resolve inside the workspace (1.362013ms)
|
||||
✔ pi's own path normalisation can't be used to step out (1.467704ms)
|
||||
✔ a symlink inside the workspace that points out is outside (1.095105ms)
|
||||
✔ a dangling symlink is refused at any depth, in both harnesses (4.272429ms)
|
||||
✔ read is checked under every spelling pi's read would open, in both harnesses (21.167594ms)
|
||||
✔ other spellings cover directories, dangling links and pi's cwd (1.677166ms)
|
||||
✔ a relative path climbs from the workspace's real path, in both harnesses (6.219838ms)
|
||||
✔ claude path fields per tool (0.7262ms)
|
||||
✔ glob patterns stay inside the workspace (0.686564ms)
|
||||
✔ a path that can't be checked is blocked (0.50161ms)
|
||||
✔ fetch_review's have lists the working refs before origin's, and no more than HAVE_MAX (320.065372ms)
|
||||
✔ fetch_review's have orders the working refs newest first within each clone, one clone after another (171.348337ms)
|
||||
✔ a runner git past its time: SIGTERM reaches its process group, and SIGKILL follows after the grace period (1808.942059ms)
|
||||
✔ once a signalled runner git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (303.226141ms)
|
||||
✔ initialize, ping and tools/list (62.762515ms)
|
||||
✔ tools/call goes through the tool socket; a refusal is an isError result (39.278044ms)
|
||||
✔ replies are compact JSON; over the cap a tool error names the size and the cap (43.90636ms)
|
||||
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (35.355401ms)
|
||||
✔ a missing argument is a usage error (35.71761ms)
|
||||
✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (452.832946ms)
|
||||
✔ pi: a reply over the cap is a tool error naming the size and the cap; list_tasks asks for open tasks (359.48369ms)
|
||||
✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (407.609399ms)
|
||||
✔ pi: a read is refused when pi would open another spelling outside (362.119518ms)
|
||||
✔ pi: a relative path climbs from the real path of a workspace behind a symlink (379.921719ms)
|
||||
✔ pi: a relative path climbs from the real path of a dataRoot behind a symlink (350.772403ms)
|
||||
✔ pi: a missing extension refuses before any model call (7.048822ms)
|
||||
✔ pi: an extension without its configuration fails pi's start (288.668919ms)
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (1.579601ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.299611ms)
|
||||
✔ turnRequest says a tester's message is a test and never Jason's instruction (0.243177ms)
|
||||
✔ turnRequest shows the message's own sent time, a day old as well as new (0.168091ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.295487ms)
|
||||
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (141.677217ms)
|
||||
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (172.84021ms)
|
||||
✔ a task closed on the tracker is missing from the next turn's open list (198.909429ms)
|
||||
✔ a message read a day after it was sent shows the time it was sent (88.509962ms)
|
||||
✔ a decision the human resolves becomes a turn citing the decision and the answer (169.468591ms)
|
||||
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (66.554151ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (58.553156ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (352.922977ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1222.134351ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (121.204369ms)
|
||||
✔ founder credentials stop before the claim (20) (91.154159ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (95.675319ms)
|
||||
✔ the launch ending under a running session exits 22 (104.266503ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (205.343038ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (46.094233ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (149.787469ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (88.082295ms)
|
||||
✔ the PM gets its task verbs and the reads, and no launch (9.367656ms)
|
||||
✔ the chief of staff gets launch and send_message, and no task verbs (2.497347ms)
|
||||
✔ the assistant gets send_message and the reads, no launch (3.990869ms)
|
||||
✔ list_requirements reads the requirement ids, for a task.create holder with a tracker (4.791673ms)
|
||||
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (1.870443ms)
|
||||
✔ a coder with a tracker gets its own task tools and no task_reassign (1.953543ms)
|
||||
✔ launch only when the business's launch block names the instance as launcher (3.505102ms)
|
||||
✔ an action outside the instance's authority has no tool (3.08216ms)
|
||||
✔ the review loop: push_working and request_review for the coder, fetch_review and review_verdict for the reviewer, none for the PM (6.597497ms)
|
||||
✔ callTool: one JSON line out, the result back, a refusal rejects (12.03723ms)
|
||||
✔ list_tasks takes the broker's filters and pages; get_task takes a task_ref (2.63551ms)
|
||||
✔ callTool sends list_tasks done false unless the session names done; other tools as given (3.288482ms)
|
||||
✔ replyText: compact JSON up to the cap, an error naming the size and the cap over it (0.980106ms)
|
||||
ℹ tests 84
|
||||
ℹ suites 0
|
||||
ℹ pass 84
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 10745.981848
|
||||
@@ -0,0 +1,145 @@
|
||||
✔ a first launch clones the root at its branch, with the root's path as origin (43.404857ms)
|
||||
✔ the clone is not at its place until git clone has finished (27.564982ms)
|
||||
✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (82.634971ms)
|
||||
✔ a clone ahead of the root keeps its commits and counts as current (50.054047ms)
|
||||
▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves
|
||||
✔ a modified file (55.814391ms)
|
||||
✔ an untracked file (51.957487ms)
|
||||
✔ a staged file (53.241086ms)
|
||||
✔ another branch (48.096881ms)
|
||||
✔ a detached HEAD (53.963358ms)
|
||||
✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (264.380479ms)
|
||||
✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (55.256863ms)
|
||||
▶ a clone kept before the branch check, or for a failed status, gets no fetch
|
||||
✔ not-a-clone (27.388417ms)
|
||||
✔ hooks-present (30.693498ms)
|
||||
✔ submodule (49.263341ms)
|
||||
✔ config-changed (38.339207ms)
|
||||
✔ root-unreadable (32.680873ms)
|
||||
✔ root-detached (41.767031ms)
|
||||
✔ status-failed, on the root's branch and clean (45.765674ms)
|
||||
✔ status-failed, dirty (45.56937ms)
|
||||
✔ a clone kept before the branch check, or for a failed status, gets no fetch (313.275551ms)
|
||||
✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (54.730505ms)
|
||||
✔ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (62.197275ms)
|
||||
✔ a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge (93.44514ms)
|
||||
▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it
|
||||
✔ user.name (33.651838ms)
|
||||
✔ user.email (31.234936ms)
|
||||
✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (65.452353ms)
|
||||
✔ a commit git can't read after the fast-forward is null (53.1729ms)
|
||||
✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (21.808732ms)
|
||||
▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it
|
||||
✔ core.fsmonitor (39.891688ms)
|
||||
✔ core.hooksPath (38.470916ms)
|
||||
✔ a hook file (38.185371ms)
|
||||
✔ include.path (37.783747ms)
|
||||
✔ includeIf (39.256286ms)
|
||||
✔ a filter driver (38.72543ms)
|
||||
✔ origin moved (39.223475ms)
|
||||
✔ a second origin url (38.093661ms)
|
||||
✔ the origin url twice, both the root (45.909975ms)
|
||||
✔ an ext:: origin (52.07397ms)
|
||||
✔ a branch tracking another remote (40.216865ms)
|
||||
✔ a branch merging a tag (41.118033ms)
|
||||
✔ a fetch line onto local branches (45.71208ms)
|
||||
✔ no origin (39.822309ms)
|
||||
✔ a config file over the size cap (35.792902ms)
|
||||
✔ config a symlink to /dev/zero (36.893189ms)
|
||||
✔ the clone a symlink to the bait (36.9515ms)
|
||||
✔ the clone a file (38.850721ms)
|
||||
✔ the clone a plain directory (36.559539ms)
|
||||
✔ .git a gitdir: file (37.751367ms)
|
||||
✔ .git a symlink (36.566473ms)
|
||||
✔ .git/logs a symlink (37.161928ms)
|
||||
✔ .git/refs/heads a symlink (36.12055ms)
|
||||
✔ .git/objects a symlink (40.875677ms)
|
||||
✔ a reflog hard-linked to a file outside (35.505026ms)
|
||||
✔ HEAD a fifo (38.967347ms)
|
||||
✔ a commondir file (39.282462ms)
|
||||
✔ an alternates file (36.477766ms)
|
||||
✔ a gitlink staged, no .gitmodules (51.117722ms)
|
||||
✔ a gitlink and .gitmodules staged (51.964361ms)
|
||||
✔ a gitlink to a repository with its own gitlink (60.573909ms)
|
||||
✔ a gitlink and .gitmodules committed (55.97076ms)
|
||||
✔ a gitlink and submodule.active in config (53.202678ms)
|
||||
✔ a .git/modules with no gitlink (38.553537ms)
|
||||
✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1414.761528ms)
|
||||
▶ a root that can't be cloned refuses with the project id and leaves nothing behind
|
||||
✔ missing (12.458554ms)
|
||||
✔ not a repository (12.839438ms)
|
||||
✔ a plain directory inside another repository (18.321853ms)
|
||||
✔ unreadable (11.43209ms)
|
||||
✔ on no branch (14.339871ms)
|
||||
✔ missing an object (18.353125ms)
|
||||
✔ a root that can't be cloned refuses with the project id and leaves nothing behind (88.967302ms)
|
||||
✔ a clone whose root is on no branch, or gone, is kept and named (33.925065ms)
|
||||
✔ no credential or forge remote of the root reaches a clone (55.220367ms)
|
||||
✔ every project in the business file gets its own clone at its own branch (39.77505ms)
|
||||
✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (516.218149ms)
|
||||
✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1019.594421ms)
|
||||
✔ a timeout signals git's process group, so a process git started dies with it (518.507345ms)
|
||||
✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (517.042897ms)
|
||||
✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (1.859159ms)
|
||||
✔ sandboxProblem names a missing bwrap and a bwrap that fails (9.624115ms)
|
||||
✔ every bind says why; the bundle and session file stay read-only over the run directory (1.451475ms)
|
||||
✔ the host's git socket is bound only when given, read-only and before the writable binds (0.963571ms)
|
||||
✔ the git identity is the instance's, at <instance>@<business>.mosaic.invalid, and sandboxArgv refuses without it (1.438803ms)
|
||||
✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (1842.958863ms)
|
||||
✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2887.39789ms)
|
||||
✔ a confined session that runs tester-cli.mjs is refused as tester, and the same shim outside the sandbox binds (215.841802ms)
|
||||
✔ resolveSeat: by name under --repo resolves the repo layout (1.643297ms)
|
||||
✔ resolveSeat: by path resolves the fleet layout (0.407826ms)
|
||||
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.764288ms)
|
||||
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.703689ms)
|
||||
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.899822ms)
|
||||
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.618498ms)
|
||||
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.39823ms)
|
||||
✔ CLI launch: registers, execs the fake launch script, and passes args through (33.906101ms)
|
||||
✔ CLI launch: --harness lands in the record (33.936108ms)
|
||||
✔ CLI launch: the launch script's own exit code passes through (44.133685ms)
|
||||
✔ CLI launch: relaunching a seat rewrites the one registration record (76.521405ms)
|
||||
✔ CLI launch: omitting --task records an empty string, not null (32.253603ms)
|
||||
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (93.240228ms)
|
||||
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (149.786589ms)
|
||||
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.502607ms)
|
||||
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.859748ms)
|
||||
✔ validateRegistration/readRegistration: taskSetBy is optional; a record without it (written before #1511) still loads unchanged; an invalid one is refused; the version does not change (0.906078ms)
|
||||
✔ 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.050485ms)
|
||||
✔ 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 (312.028942ms)
|
||||
✔ family: exactly one launch.max key in the model name, else null (0.759209ms)
|
||||
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.76273ms)
|
||||
✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.300908ms)
|
||||
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.682661ms)
|
||||
✔ session file and launch log: 0600, the session file written once (1.14756ms)
|
||||
✔ endReason maps the runner's exit codes; a signal is killed (0.118692ms)
|
||||
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.2617ms)
|
||||
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (1.277834ms)
|
||||
✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (248.955506ms)
|
||||
✔ workRef and validBranch take only what a ref name can hold (1.430498ms)
|
||||
✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (131.943134ms)
|
||||
✔ a rewritten branch refuses non-fast-forward and the work ref stays (112.262522ms)
|
||||
✔ a branch with no history in common with main refuses unrelated-history (54.785663ms)
|
||||
✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (45.354562ms)
|
||||
✔ main is fetched from the root again before each push, so a clone of a newer root pushes (113.360079ms)
|
||||
✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (76.724472ms)
|
||||
✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (81.551961ms)
|
||||
✔ only a v2 bundle with the named branch is read (61.52326ms)
|
||||
✔ a branch named main, or named for another task, moves only this task's work ref (91.420396ms)
|
||||
✔ a record that throws moves the work ref back, or removes a new one (137.536251ms)
|
||||
✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (396.410578ms)
|
||||
✔ a root that can't be read refuses root-unreadable (32.319413ms)
|
||||
✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2128.687725ms)
|
||||
✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1032.500747ms)
|
||||
✔ a working repository path that isn't a directory refuses (28.304368ms)
|
||||
✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (132.338564ms)
|
||||
✔ review of a project with no working repository refuses unknown-commit (16.327165ms)
|
||||
✔ the runner's git tools hold the host's limits and branch rule (1.220025ms)
|
||||
ℹ tests 132
|
||||
ℹ suites 0
|
||||
ℹ pass 132
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 5459.944449
|
||||
@@ -0,0 +1,100 @@
|
||||
✔ the boot config is checked before anything starts (25.781318ms)
|
||||
✔ a business with no tracker entry refuses task verbs (17.508069ms)
|
||||
✔ credential.expiring and .expired are recorded once per instance (37.706391ms)
|
||||
✔ a token file that changes on disk records credential.changed (17.768487ms)
|
||||
✔ autostart polls, reconciles and retries a startup the tracker was down for (42.257219ms)
|
||||
✔ a refusal a restart must clear is not retried by the poll (13.768382ms)
|
||||
✔ a poll that fires while two are queued is dropped (16.150669ms)
|
||||
✔ close waits for a running verb and refuses one that has not started (53.742099ms)
|
||||
✔ the bundled Vikunja is the pinned upstream image the runbook names (1.024956ms)
|
||||
✔ every published port is on 127.0.0.1, and no secret is in the file (0.734362ms)
|
||||
✔ the committed table loads frozen; its verbs are the broker task verbs (3.023018ms)
|
||||
✔ a write names a verb the table has as its own key, not one it inherits (1.03756ms)
|
||||
✔ the schema refuses any other shape (5.14694ms)
|
||||
✔ the Field table in the slice 1 plan names the verbs each field is written by (1.226542ms)
|
||||
✔ each verb refuses, asks for a decision and writes as the table and the role files say (249.859558ms)
|
||||
✔ the list holds the PRD's requirement ids, in its order, at its version (2.254163ms)
|
||||
✔ the committed list loads frozen (1.247074ms)
|
||||
✔ the schema refuses any other shape (6.356786ms)
|
||||
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (126.555819ms)
|
||||
✔ the project list holds recorded project objects in the recorded list envelope (13.500261ms)
|
||||
✔ the recorded task bodies pass the checks S3 applies to every read (0.574667ms)
|
||||
✔ the client works against the fake over real HTTP with the platform fetch (131.415103ms)
|
||||
✔ a correct install starts, and the first reconcile records tasks that already exist (37.744797ms)
|
||||
✔ verbs refuse while a business is starting and after startup refused it (19.578388ms)
|
||||
✔ startup refuses a token that can do more than its role needs (49.362086ms)
|
||||
✔ startup refuses an unsupported version and flags an untested one (39.671867ms)
|
||||
✔ startup refuses a board that the runbook did not install (49.617591ms)
|
||||
✔ startup refuses a project the sync bot cannot read (12.586776ms)
|
||||
✔ startup refuses a configured label the pm bot cannot see (12.607427ms)
|
||||
✔ startup refuses an expired credential and a missing sync credential (19.620313ms)
|
||||
✔ an unreachable tracker refuses with tracker-unavailable (9.644199ms)
|
||||
✔ an edit in the UI is recorded once, with the fields that changed (92.127393ms)
|
||||
✔ a move between open buckets is seen on the board, though updated does not change (46.844419ms)
|
||||
✔ a person's comment is counted and a bot's is not (82.334504ms)
|
||||
✔ the hourly reconcile catches a comment through comment_count (87.307151ms)
|
||||
✔ a task closed in the UI leaves the open view with its done bucket (120.199919ms)
|
||||
✔ a task that leaves the board is recorded as deleted, moved or out of reach (70.901013ms)
|
||||
✔ a poll that read before a verb wrote does not overwrite the verb (42.364137ms)
|
||||
✔ a tracker fault during a tick is reported and the next tick catches up (37.188458ms)
|
||||
✔ a malformed answer refuses the tick with tracker-shape (13.932165ms)
|
||||
✔ no token value reaches the database, the log or a refusal (50.334001ms)
|
||||
✔ the first look at a task counts only comments inside the window (34.537002ms)
|
||||
✔ with descendants off the adapter reads no project list and no child, as in v0 (54.761806ms)
|
||||
✔ a three-level tree is read per project, and a project outside it is not (26.431563ms)
|
||||
✔ a watch-only child keeps the business ready, and a failed child read refuses as transient (31.736429ms)
|
||||
✔ a root without the installed buckets refuses with descendants on (12.711468ms)
|
||||
✔ the project list refuses without read_all, and skips archived projects and saved filters (31.050596ms)
|
||||
✔ descendants must be true or false (13.456072ms)
|
||||
✔ a move inside the tree is a moved tombstone plus a snapshot under the new ref (69.89001ms)
|
||||
✔ a child made after startup joins the tree at the next reconcile (58.337455ms)
|
||||
✔ a child that leaves the tree has its tasks recorded missing, and the ledger keeps their history (62.372931ms)
|
||||
✔ a child deleted or hidden between reconciles drops out at the next tick, and the root still refuses (87.445447ms)
|
||||
✔ a reconcile that fails keeps the previous tree (37.978164ms)
|
||||
✔ a closed task the cursor missed takes its own project's done bucket (39.188431ms)
|
||||
✔ closed tasks read at startup take their own project's done bucket (11.935044ms)
|
||||
✔ a closed task the cursor returns takes its own project's done bucket (36.961131ms)
|
||||
✔ a task moved out of a child that leaves the tree at the same time is moved, not no-access (34.029839ms)
|
||||
✔ a task in a bucket outside the five keeps its raw bucket id (31.570071ms)
|
||||
✔ bucket 0 on a watch-only task never becomes a bucket or a state (79.409698ms)
|
||||
✔ a child whose kanban view is deleted still refuses the tick, and the reconcile reads it watch-only (57.745456ms)
|
||||
✔ a child dropped at a tick comes back at the next reconcile once the bot reads it again (34.754159ms)
|
||||
✔ task.create needs a recorded human request and a requirement id (55.066915ms)
|
||||
✔ task.create takes only an approved requirement id (30.665227ms)
|
||||
✔ task.create cites the request a received message carries, not the message's id (24.081762ms)
|
||||
✔ only labels named in the business file can be written (39.334465ms)
|
||||
✔ task.schedule sets and clears a due date and relations (45.777395ms)
|
||||
✔ assign and reassign move the role bots and record task.assigned (68.753853ms)
|
||||
✔ task.update.assigned is for the assignee and records task.state (53.099643ms)
|
||||
✔ a wrong expected digest records task.conflict and writes nothing (19.845705ms)
|
||||
✔ a cross-role verb needs a resolved decision, used once (25.311339ms)
|
||||
✔ task.close needs a verdict; after it every verb refuses with task-done (27.567547ms)
|
||||
✔ a task with pushed work closes only citing an approve verdict for its latest push, and a refusal writes nothing (36.52325ms)
|
||||
✔ a lost answer is settled by a re-read and never retried (30.529512ms)
|
||||
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (43.212972ms)
|
||||
✔ a task the sync bot cannot read refuses and records nothing (19.876207ms)
|
||||
✔ verbs and polls for one business run one at a time (93.692392ms)
|
||||
✔ a due date with milliseconds is written to the second (65.837523ms)
|
||||
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (38.157924ms)
|
||||
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (14.680481ms)
|
||||
✔ a create whose final read fails succeeds and records task.created (33.553686ms)
|
||||
✔ an edit between the last write and the final read shows as external on the next poll (35.950536ms)
|
||||
✔ task.created is recorded when a later label write fails (12.424119ms)
|
||||
✔ every verb acts in an installed child, on the child's own board (47.068002ms)
|
||||
✔ task.create refuses a project outside the tree, a watch-only one and one that is not an id (24.211461ms)
|
||||
✔ with descendants off, task.create takes only the root project (12.415245ms)
|
||||
✔ a verb on a watch-only task, or a relation to one, refuses with project-not-installed (17.815602ms)
|
||||
✔ a related task is read before authority: in another project than its ref names, task-moved; missing, task-not-found (23.59126ms)
|
||||
✔ a task moved to another project of the tree refuses its old ref with task-moved and takes its new one (16.31585ms)
|
||||
✔ a verb and a create in the child record the final read of the child's board (14.544316ms)
|
||||
✔ a create in the child whose final read fails records the child's todo bucket (34.602144ms)
|
||||
✔ a lost answer in the child is settled by a re-read of the child's board (20.233963ms)
|
||||
✔ every related task is read before authority, not only the first (16.821789ms)
|
||||
ℹ tests 92
|
||||
ℹ suites 0
|
||||
ℹ pass 92
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1171.659914
|
||||
@@ -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,117 @@
|
||||
✔ the Board: held roles with no machine sessions, a claim with no address, the toggle kept across a reload, and Message addressed to the role (7450.917035ms)
|
||||
✔ a redirect answer on a Board read, a bus read, a conversation read, a board POST, a module read and a send signs the page out: one line, nothing stale, no further reads (6027.399527ms)
|
||||
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5009.22866ms)
|
||||
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
|
||||
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (6684.717767ms)
|
||||
✔ the README says a refused field table names the file or the key at fault, never a path (1.613208ms)
|
||||
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (8848.968379ms)
|
||||
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (138.256803ms)
|
||||
✔ decisions route serves the roles and recent lists from the reader view (101.782201ms)
|
||||
✔ credentials route serves the broker credential status from the reader view (38.135388ms)
|
||||
✔ replies route serves the replies to one message from the reader view (139.194389ms)
|
||||
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (286.901522ms)
|
||||
✔ bus refusals map to statuses and never carry a path or a stack (257.058587ms)
|
||||
✔ humanCall speaks the human transport protocol without blocking the server (3493.646637ms)
|
||||
✔ busReader takes --business, else the live bus host, else refuses (592.672538ms)
|
||||
✔ humanCall kills a transport that runs past its timeout (545.114965ms)
|
||||
✔ serve refuses a --business value that is not a business id (1119.36074ms)
|
||||
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5055.64655ms)
|
||||
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3386.620311ms)
|
||||
✔ conversation view: a newer session with no readable history keeps the marker (1484.085042ms)
|
||||
✔ conversation view: seats without history say so and offer no reply (1013.457164ms)
|
||||
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3781.62246ms)
|
||||
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54795.919858ms)
|
||||
✔ messages HTTP read pages and preserves metadata; bad arguments refuse before a read; other reads unchanged (177.100893ms)
|
||||
✔ Console Messages lists late unsolicited messages on polling with sender class time read state and trail; nav count includes beyond the page; reads never deliver (5145.553357ms)
|
||||
✔ Messages nav count polls on Tasks and Board and clears on a Board refusal; messages 403 refuses local pages (3800.760001ms)
|
||||
✔ copy writes the Console paths at the full id, read-only, and leaves an existing copy alone (4357.740243ms)
|
||||
✔ copy refuses a commit off refactor, an unknown one, a worktree and a bad copies directory (2951.697706ms)
|
||||
✔ copy writes nothing when an archived path is missing at the commit (657.455292ms)
|
||||
✔ a copy that loses the rename race leaves no read-only temp directory behind (1161.981026ms)
|
||||
✔ a copy whose rename fails with no target refuses, writes no copy and leaves no temp directory (620.290167ms)
|
||||
✔ a copy that meets a target holding files says exists: and moves nothing into it (741.267322ms)
|
||||
✔ a copy that meets an empty target directory replaces it by rename, not by moving into it (461.892323ms)
|
||||
✔ serve runs from a copy: /healthz 200, the config check, roles and RELEASE from the copy, the queue from --checkout (920.664419ms)
|
||||
✔ serve refuses a --checkout that is not an absolute directory path (238.290799ms)
|
||||
✔ consoleReads runs the queue read under queueRoot and reads RELEASE under root (51.388122ms)
|
||||
✔ preview options are all three or none, and each is checked (13.695009ms)
|
||||
✔ the CLI refuses a partial or invalid preview flag set at start (2737.160843ms)
|
||||
✔ the CLI with all three flags serves the preview mode (774.189948ms)
|
||||
✔ a request for the public name needs the header equal to the user id, else 401 and nothing read (67.987951ms)
|
||||
✔ the header name matches without regard to case, and the value compare is exact (8.423834ms)
|
||||
✔ the public name with the header reads the bus views, and the Host and Origin are checked first (72.365765ms)
|
||||
✔ in preview mode every write answers 403 read-only, from the public name and loopback, and reaches nothing (27.743392ms)
|
||||
✔ loopback requests are unchanged in preview mode, and the bus views work with the board down (10.053997ms)
|
||||
✔ a proxy that rewrites Host to the loopback port gets 403 on its first request, whatever the user (63.945506ms)
|
||||
✔ a repeated Host is 400 before any other check, in preview mode and without it (16.007107ms)
|
||||
✔ without the preview flags the public name is refused and writes still reach the board (14.249158ms)
|
||||
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (7.818438ms)
|
||||
✔ credential state from the date alone, as the bus states it (0.322931ms)
|
||||
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2713.763077ms)
|
||||
✔ queue: a refused read fails closed with the store's text, redacted (1062.919501ms)
|
||||
✔ queue: a store that fails to load, or a child that dies, gives a fixed message with no path or stack (2563.40116ms)
|
||||
✔ business: names, vars on the allowlist, authority, credential metadata only (55.707507ms)
|
||||
✔ business: missing or invalid file refuses with the module's text, redacted (46.901887ms)
|
||||
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (51.987087ms)
|
||||
✔ settings: release, board, loopback address, notifier binding name only (503.048136ms)
|
||||
✔ task fields: the committed table with no reads configured, GET only; a bad table refuses with no path (91.768465ms)
|
||||
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4056.725083ms)
|
||||
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23593.757427ms)
|
||||
✔ the send box: held roles, the draft and caret across a refresh, each receipt, one POST per Send, and the trail link; the board Reply on a 4xx and a 5xx (7305.549721ms)
|
||||
✔ a sent receipt lists its replies as the page refreshes, oldest first and capped, and marks none delivered or read (11983.82136ms)
|
||||
✔ startServer takes send only as a function and only with the preview mode (11.371981ms)
|
||||
✔ the CLI refuses --send without the preview flags, and without a configured bus (1325.682991ms)
|
||||
✔ the CLI with --send and a configured bus sends through the human transport and logs one line (502.924376ms)
|
||||
✔ without --send the route is read-only like every other write, and config says no send (109.032648ms)
|
||||
✔ with --send every other write is still 403 read-only, and nothing reaches the board or bus (108.168615ms)
|
||||
✔ config says send only on the public name with --send on (31.037411ms)
|
||||
✔ a send without the signed-in user is 401 and is neither sent nor logged, loopback included (37.343399ms)
|
||||
✔ each request check refuses before anything is sent, and logs the refusal without the body (132.345868ms)
|
||||
✔ a send passes exactly to, body and in_reply_to, answers the id, and logs it (19.773191ms)
|
||||
✔ a crafted role name cannot forge a log line (8.624604ms)
|
||||
✔ a bus refusal is not sent, with its status; an outcome the bus could not confirm is unknown and never retried (37.233355ms)
|
||||
✔ through the human transport the request is exactly the business, message.send and the keys (386.840742ms)
|
||||
✔ against a real broker: the message is written once as a human send, and its refusals map (129.763539ms)
|
||||
✔ loopback host and board origin fail closed (28.343991ms)
|
||||
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (228.012193ms)
|
||||
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (154.92373ms)
|
||||
✔ unreachable board reports URL; CLI rejects unsupported options (1290.643505ms)
|
||||
✔ render prints the unit without the preview flags (67.052587ms)
|
||||
✔ render carries every serve flag, the preview set included (42.735544ms)
|
||||
✔ a partial preview set is refused (99.344251ms)
|
||||
✔ a value the unit cannot carry is refused, for every flag (1787.929448ms)
|
||||
✔ the rendered unit names none of the human proof agent markers and sets no agent variable (180.906207ms)
|
||||
✔ install into --dir with --no-reload writes the file whole, then reports unchanged (353.63869ms)
|
||||
✔ install without --no-reload runs daemon-reload and nothing that starts the unit (191.787045ms)
|
||||
✔ uninstall refuses while the unit is running, with or without --no-reload, and removes it once it is not (594.963695ms)
|
||||
✔ status prints the unit state and GET /healthz on the installed port (622.478764ms)
|
||||
✔ usage: an unknown verb or flag, and start or enable, exit 4 (302.210319ms)
|
||||
✔ without --send, render is the template with its placeholders filled, byte for byte (138.262474ms)
|
||||
✔ render --send with the preview set ends ExecStart with --send and changes nothing else (163.617822ms)
|
||||
✔ --send without the whole preview set is refused and writes nothing (329.849117ms)
|
||||
✔ install --send into --dir with --no-reload writes the sending unit, and status says whether it sends (350.11987ms)
|
||||
✔ uninstall and status take no --send (45.710351ms)
|
||||
✔ install refuses while the unit is running, in any state but inactive or failed, and changes nothing (381.227682ms)
|
||||
✔ when the state read fails, install and uninstall refuse with one line and change nothing (107.315944ms)
|
||||
✔ install needs --copy naming a copy that exists, and refuses outside the canonical checkout without --allow-worktree (141.394312ms)
|
||||
✔ install refuses unless the bus unit runs from the same copy, naming both paths, and changes nothing (481.342049ms)
|
||||
✔ render --copy runs the copy and reads the queue in the checkout; without --copy the unit is unchanged (19.693826ms)
|
||||
✔ the uid header takes only an Authentik uid; the username header still takes a username (428.286087ms)
|
||||
✔ a preview value cannot start with - or . (40.381367ms)
|
||||
✔ status names the copy and whether refactor has moved past it, and says when the unit runs from no copy (495.525432ms)
|
||||
✔ tokens.css and icons.svg are byte copies of docs/design (1.760157ms)
|
||||
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (8.883249ms)
|
||||
✔ shipped strings are unchanged, and the web keeps its write boundary (4.039573ms)
|
||||
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7697.078635ms)
|
||||
✔ five states on Board, Inbox, Tasks, Agents and Trail (2436.803941ms)
|
||||
✔ Queue, Business and Settings in their five states, with no move button (4933.832887ms)
|
||||
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1367.213279ms)
|
||||
✔ the server log names no config path when the system config refuses (314.418886ms)
|
||||
ℹ tests 108
|
||||
ℹ suites 0
|
||||
ℹ pass 108
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 55425.728446
|
||||
Reference in new issue
Block a user