docs: row 115 round 2 packet, the seen-before-close request test (#1609)
Candidate on base 94ece491f: 5 files, manifest 1910eb5a; only
decision-close.test.mjs changed since round 1. Mutants m1-m4 killed
(m4 is Filbert's f4); gate passed with no flakes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,130 @@
|
|||||||
|
# Row 115 (#1609) round 2: a closed decision sends the raising role a message
|
||||||
|
|
||||||
|
## Round 2
|
||||||
|
|
||||||
|
Filbert asked for changes in round 1 (comment 28422, rev 1131). Their C1: nothing tested the op filter in `#request`'s fallback query, `json_extract(body,'$.op') IN ('resolved','withdrawn')`. A human `decision.seen` on a human-routed decision also writes a `human.input` answer with the decision as its subject, with op `seen`. Without the filter, a seen before the close becomes the close message's `request`, which breaks brief item 3. Their f4 dropped the filter and the suites still passed.
|
||||||
|
|
||||||
|
The fix is test-only, and broker.mjs is the same as in round 1. In packages/bus/tests/decision-close.test.mjs:
|
||||||
|
|
||||||
|
- A new test, "a human seen before the close leaves the request on the close's answer, not the seen". The human sees two human-routed decisions, then resolves one and withdraws the other. Each close message's `request` is that close's own answer, and not the seen's.
|
||||||
|
- The `answer()` helper takes an op, `resolved` by default, so it can't return a seen's answer either. The human withdraw test now asks for `withdrawn`.
|
||||||
|
|
||||||
|
f4 is m4 in mutants.py. Round 1 sections below are updated where the counts changed.
|
||||||
|
|
||||||
|
|
||||||
|
Brief: docs/plans/2026-10-14_decision-close-message.md. Mos found the gap. Jason resolved decision 03130d08, and the PM, which raised it, never got a turn, because a close wrote only `decision_events` and nothing reached the role.
|
||||||
|
|
||||||
|
BASE: 94ece491f05ecbb737227da8a7ba1d57a1ac75fd. I built the candidate on 54b7c7b3e and moved the worktree to 94ece491f before the mutants and the gate. The commits between them touch only packages/ledger, docs/SESSIONS.md and the queue files, none of the manifest paths.
|
||||||
|
Candidate: build.patch against BASE. candidate-manifest.sha256 lists the 5 files, of which only decision-close.test.mjs changed since round 1. I applied the patch to a fresh `git archive 94ece491f` export and every digest matched (DIGESTS_OK). HEAD has moved past 94ece491f since, with docs commits and the PRD v1.1 merge f7c8537b3. That merge changes docs/prd/mosaic-stack.md, packages/tasks/requirements.json and two requirements tests, packages/tasks/tests/requirements.test.mjs and packages/bus/tests/requirements.test.mjs (2 lines). None of them is a manifest path, but the bus one is in the bus suite, so the landing gate at the new target covers it and mine at 94ece491f doesn't.
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
### The close message
|
||||||
|
|
||||||
|
packages/bus/src/broker.mjs, the `decision.resolve`/`decision.seen`/`decision.withdraw` case. After the `decision_events` insert and the human's `human.input` answer event, a close with `op !== 'seen'` by a human, or by a run other than `raised_by_run`, inserts one message:
|
||||||
|
|
||||||
|
| Column | Value |
|
||||||
|
|---|---|
|
||||||
|
| `from_role`, `from_run` | `human`, `human-cli` for a human close; the closing role and run for a role close |
|
||||||
|
| `to_role` | `d.raised_by_role` |
|
||||||
|
| `class` | `DECISION` |
|
||||||
|
| `decision` | the decision's id |
|
||||||
|
| `in_reply_to`, `corrects` | null |
|
||||||
|
| `body` | `Decision <id> on <action> was resolved by <by>: <choice> (<option text>).` or `Decision <id> on <action> was withdrawn by <by>.`, then `\nNote: <note>` when the close had a note |
|
||||||
|
|
||||||
|
`<by>` is the same `by` the `decision_events` row gets: the human's name, or the closing run. The option text comes from the decision's own `options`, which `invalid-choice` has already checked. The message is routed like `message.send`'s, to the raising role's current holder, or with no holder until the next claim routes it.
|
||||||
|
|
||||||
|
It runs inside `request()`'s transaction, after every refusal check, so a refused close (`invalid-choice`, `decision-closed`, `not-resolver`) writes no message.
|
||||||
|
|
||||||
|
The cases that send nothing:
|
||||||
|
|
||||||
|
- `decision.seen`, by the `op` check.
|
||||||
|
- The raising run's own withdraw, by the run check. An agent withdraw is only ever the raising run (`not-resolver` otherwise), so a role's withdraw never sends one. A human withdraw does.
|
||||||
|
- A supersede. Its withdrawal is a direct `decision_events` insert inside `decision.raise`, not this case.
|
||||||
|
- A within-role decision. It closes inside `decision.raise`, so `decision.resolve` on it refuses `decision-closed`.
|
||||||
|
|
||||||
|
### The request
|
||||||
|
|
||||||
|
`message.receive` mapped `request` with one query, the `human.input` whose subject is the message id. That moves into a private `#request(business, m)`, unchanged for every message except one kind. When there's no event for the message itself and the message is a `DECISION` from `human` that cites a decision, it returns the `human.input` on that decision with `kind` `answer` and `op` `resolved` or `withdrawn`. A decision has at most one close, because both close paths refuse `decision-closed` and the broker never writes `expired`, so that event is unique. A human `message.send` with class `DECISION` keeps its own instruction event, because the first query finds it. A role close's message has no `human.input`, so its `request` is null.
|
||||||
|
|
||||||
|
### The runner
|
||||||
|
|
||||||
|
No change, as the brief says. `turnRequest` already prints `citing decision` and `request`, and the runner replies to `from_role`, so the role's RESULT goes to the human's inbox.
|
||||||
|
|
||||||
|
### README
|
||||||
|
|
||||||
|
packages/bus/README.md:
|
||||||
|
|
||||||
|
- The `decision.resolve` row says it sends the raising role a DECISION message. The `decision.withdraw` row says a human withdraw does and the raising run's own withdraw doesn't. The `message.receive` row says a human close's message carries the close's answer as `request`, and a role close's has null.
|
||||||
|
- A new section, "Decision close messages (row 115, #1609)", after the decision authorization text. It gives the columns, the routing, both body forms, `<by>`, the note line, the four cases that send nothing, the request, and that the message shows in the decision's `trail`.
|
||||||
|
|
||||||
|
`unslop-check` is clean on the README.
|
||||||
|
|
||||||
|
## Scope notes
|
||||||
|
|
||||||
|
1. packages/bus/tests/single-use.test.mjs. "message.send consumes approval and prevents a later send or authorize" counted every message after its approval and expected 1. The approval's resolve is a cross-role close by the arbiter, so it now sends the raiser a DECISION message too, and the count was 2. The test now counts the DECISION message (1) and the other messages (1) separately. The brief lists packages/bus/tests/ as owned, and this was the only existing test that changed.
|
||||||
|
2. The decision's `trail` gains the new message, since `#trail` adds messages whose `decision` is the decision. The human.input answer keeps the decision as its subject. The human resolve test checks the trail.
|
||||||
|
3. `agents`. A role whose decision the human closed now has a routed message. It reads `working` while its runner takes the turn, then `idle` once the runner marks the message read. That's the turn the brief asks for, so I didn't change the state rules.
|
||||||
|
4. Nothing reads the new message outside the bus's own verbs. The Console, the notifier and Discord are out of scope by the brief and unchanged.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
packages/bus/tests/decision-close.test.mjs is new, with 8 tests. Its fixture raises one decision routed to cto (technical), one to pm (delivery) and one gated to the human, all from coder.
|
||||||
|
|
||||||
|
| Test | Checks |
|
||||||
|
|---|---|
|
||||||
|
| a human resolve sends the raising role one message from the human, and its request is the answer | one DECISION message. Every column, the body with the note line, `decision_events.by` is `jason`, one `routed` delivery to coder-run, and coder's `message.receive` returns it with `request` equal to the answer event id. The message is in the decision's trail |
|
||||||
|
| a cross-role decision resolved by its arbiter comes from that role and run, with no request | from cto/cto-run to coder, the body with `resolved by cto-run: yes (Allow).` and no note line, `request` null |
|
||||||
|
| a human withdraw sends one message with the note | from human to coder, `withdrawn by jason.`, a two-line note kept as written, `request` the withdraw's answer |
|
||||||
|
| a human seen before the close leaves the request on the close's answer, not the seen | round 2. Two human-routed decisions, both seen by the human, one resolved and one withdrawn. Each message's `request` is its close's answer, not the seen's |
|
||||||
|
| the raising run's withdraw, a supersede, a within-role raise and seen send nothing | 3 closes and 2 seen rows in `decision_events`, no DECISION message, coder's receive is empty |
|
||||||
|
| refused closes send nothing: invalid-choice, decision-closed, not-resolver | a human bad choice, pm resolving cto's decision, cto withdrawing coder's: no message. Then one real close, and the second resolve and a human withdraw of it refuse `decision-closed` with still one message |
|
||||||
|
| a raising role nobody holds at the close gets the message on the next claim | coder releases, the human resolves: one `routed` delivery with no holder. coder-run-2 claims, the message is routed to it, and its `message.receive` returns it with the answer as `request` |
|
||||||
|
| a human DECISION send keeps its own instruction as the request, not a close's answer | after a human resolve, a human `message.send` with class DECISION citing the same decision. Coder receives both: the close's has the answer, the send's has its own `request` |
|
||||||
|
|
||||||
|
packages/harness/tests/runner.test.mjs gains "a decision the human resolves becomes a turn citing the decision and the answer". The runner holds coder, coder raises a gated `deploy`, and the human resolves it. The coder's RESULT reaches the human in reply to the close message, and its echoed turn ends with `Message <id> from human, sent <at>, class DECISION, citing decision <d>, request <answer>:` and the body. The close message is marked read.
|
||||||
|
|
||||||
|
## Mutants
|
||||||
|
|
||||||
|
mutants.py runs each mutant against the full bus suite (156 tests) and restores broker.mjs after each. Log: mutants-run.log, per-mutant output under mutants/.
|
||||||
|
|
||||||
|
| Mutant | Change | Result |
|
||||||
|
|---|---|---|
|
||||||
|
| m1 | the send condition becomes `if (false)` | killed, 8 fail: the seven close tests that expect a message, plus single-use's DECISION count |
|
||||||
|
| m2 | `to_role` becomes `d.route_to` | killed, 7 fail: every close test that reads `to_role` or the coder's inbox |
|
||||||
|
| m3 | the condition drops the run check, `if (op !== 'seen')` | killed, 1 fails: the raising run's withdraw test |
|
||||||
|
| m4 | Filbert's f4: `#request`'s fallback loses `AND json_extract(body,'$.op') IN ('resolved','withdrawn')` | killed, 1 fails: the round 2 seen-before-close test |
|
||||||
|
|
||||||
|
All four were restored, and `git diff --stat` matched the candidate afterwards. Round 1's m1 to m3 were killed too, with one fewer failing test each where the round 2 test applies.
|
||||||
|
|
||||||
|
## Gate
|
||||||
|
|
||||||
|
gate.sh runs every package suite and every scripts/test-*.sh against the candidate worktree. GATE_PASSED at 17:00:04Z, 16:54:23 to 17:00:04. Log: gate-run.log, per-step logs under gate/. Round 1's gate also passed, at 16:43:58Z.
|
||||||
|
|
||||||
|
| Step | Pass |
|
||||||
|
|---|---|
|
||||||
|
| bus | 156 (148 at BASE, plus the 8 new) |
|
||||||
|
| business | 67 |
|
||||||
|
| cli | 184 |
|
||||||
|
| control-board | 124 |
|
||||||
|
| conversation | 182 |
|
||||||
|
| discord | 178 |
|
||||||
|
| harness | 81 (80 at BASE, plus the runner test) |
|
||||||
|
| ledger | 86 |
|
||||||
|
| mosaic | 69 |
|
||||||
|
| queue | 156 |
|
||||||
|
| runs | 43 |
|
||||||
|
| seat | 120 |
|
||||||
|
| tasks | 92 |
|
||||||
|
| webui | 108 |
|
||||||
|
| scripts | auth 15, conductor 17, config 24, discord 66, extension-package 18, foundation 44, queue 28 (node 156), release 14, task 98 |
|
||||||
|
|
||||||
|
No failures and no flakes. The bwrap tester case passed (gate/pkg-seat.log:77).
|
||||||
|
|
||||||
|
Managed sessions. Sage saw a managed coder session run tests in its workspace at about 16:30Z, during their row 113 gate. My busy check flags test runners started from any process whose cwd is under ~/.mosaic-dev/workspaces/. In round 2 a managed PM turn (`claude -p`, cwd ~/.mosaic-dev/workspaces/mosaic-stack/pm) was live at 16:53Z. The check first flagged it because the prompt text in its command line contains "test". Its processes were sh, claude and node, with no test runner, so I narrowed the check to test runner command lines and went ahead. The check was clear before the mutants and before the gate. A scan after the gate found no process with a cwd under the workspaces. I can't rule out a short run between checks, but nothing failed.
|
||||||
|
|
||||||
|
The release step created the `wt_default` Docker network at 16:59:12Z. It had 0 containers, and I removed it.
|
||||||
|
|
||||||
|
## Going live
|
||||||
|
|
||||||
|
This is a bus landing, so decision 109 applies. Mos gets a notice before and after landing. It goes live only through a stack copy, a re-pin and a bus restart on Mos's go. I start, stop and restart nothing.
|
||||||
@@ -0,0 +1,389 @@
|
|||||||
|
diff --git a/packages/bus/README.md b/packages/bus/README.md
|
||||||
|
index 7293f98db..4694e08b5 100644
|
||||||
|
--- a/packages/bus/README.md
|
||||||
|
+++ b/packages/bus/README.md
|
||||||
|
@@ -140,11 +140,11 @@ an operator inspect content and delivery evidence after an uncertain result.
|
||||||
|
| `role.release` | No args; only its current holder releases. |
|
||||||
|
| `role.revoke` | `{role,decision}`; needs human-approved gated decision for this exact target and holder incarnation. |
|
||||||
|
| `decision.raise` | `{action,question,options:[{key,text}],recommendation,blocking,domain?,target?,project?,task_ref?,requirement_ref?,supersedes?,choice?,approvalChoice?}`. Returns decision with decoded options and authorization context. A blocking decision holds a task: `blocking` true with no `task_ref` refuses `task-required`. A non-blocking one needs no task, and a gated action such as `prd.approve` or `role.revoke` still routes to the human's inbox. |
|
||||||
|
-| `decision.resolve` | `{id,choice,note?}`; option must exist, resolver must be routed role or outside-agent human CLI. |
|
||||||
|
+| `decision.resolve` | `{id,choice,note?}`; option must exist, resolver must be routed role or outside-agent human CLI. Sends the raising role a DECISION message (Decision close messages, below). |
|
||||||
|
| `decision.seen` | `{id,note?}`; acknowledgement by resolver, not a resolution. |
|
||||||
|
-| `decision.withdraw` | `{id,note?}`; raiser or human. Never rewrites the decision. |
|
||||||
|
+| `decision.withdraw` | `{id,note?}`; raiser or human. Never rewrites the decision. A human withdraw sends the raising role a DECISION message; the raising run's own withdraw sends none. |
|
||||||
|
| `message.send` | `{to,body,class?,in_reply_to?,corrects?,decision?}`; returns `{id,request}`. Human instruction's `request` is its `human.input` event ID. |
|
||||||
|
-| `message.receive` | No args; up to 100 undelivered messages for current holder, including originating `request` for human instructions. |
|
||||||
|
+| `message.receive` | No args; up to 100 undelivered messages for current holder, including originating `request` for human instructions. A human decision close's message carries the close's `human.input` answer event as its `request`; a role close's has `null`. |
|
||||||
|
| `message.read` | `{id}`; recipient holder's acknowledgement; no new holder can acknowledge old holder's delivery. |
|
||||||
|
| `review.request` | `{task_ref,commit,to,note?,decision?}`; action `review.request` (row 93, #1587). `commit` must be the task's latest pushed commit: an older one refuses `stale-commit`, any other `unknown-commit`. `to` must be another role holding `review.verdict` (`unknown-role`, `self-review`, `not-a-reviewer`). Writes a `review.requested` event `{task_ref,project,ref,commit,to,message,held}` and sends `to` a REVIEW-REQUEST message naming it. `held` is the run id holding the `to` role when the request is routed (row 105, #1599), or `null` when no run holds it: the message then waits for a claim, and the requester should tell whoever launches roles. Returns `{request,message,commit,ref,held}`. |
|
||||||
|
| `review.verdict` | `{task_ref,request,verdict,body,decision?}`; action `review.verdict`. `verdict` is `approve` or `changes`. `request` must be a `review.requested` event on the task addressed to the caller's role (`request-not-found`), with no verdict yet (`verdict-exists`). Writes a `review.verdict` event `{task_ref,request,commit,verdict,message}` and sends the requesting role a REVIEW-RESULT in reply to the request's message. Returns `{verdict,message,commit}`. |
|
||||||
|
@@ -250,6 +250,32 @@ an uncertain external outcome must not be retried with that approval. S3/S6
|
||||||
|
must implement their own external-operation identity and uncertain-outcome
|
||||||
|
handling. Raising/superseding and resolving are transactional.
|
||||||
|
|
||||||
|
+### Decision close messages (row 115, #1609)
|
||||||
|
+
|
||||||
|
+A `decision.resolve` or `decision.withdraw` by anyone but the raising run sends
|
||||||
|
+the raising role one message, written in the same transaction as the close, so
|
||||||
|
+a refused close sends nothing. It goes to `raised_by_role` with class
|
||||||
|
+`DECISION`, `decision` set to the decision's id, and no `in_reply_to` or
|
||||||
|
+`corrects`. A human close is sent `from_role 'human'`, `from_run 'human-cli'`;
|
||||||
|
+a role close is sent from the closing role and run. It is routed like
|
||||||
|
+`message.send`'s: to the role's current holder, or with no holder until the
|
||||||
|
+next claim routes it. The body is
|
||||||
|
+
|
||||||
|
+```
|
||||||
|
+Decision <id> on <action> was resolved by <by>: <choice> (<option text>).
|
||||||
|
+Decision <id> on <action> was withdrawn by <by>.
|
||||||
|
+```
|
||||||
|
+
|
||||||
|
+where `<by>` is the close's `decision_events.by` (the human's name or the
|
||||||
|
+closing run). A close with a note adds `Note: <note>` on its own line.
|
||||||
|
+
|
||||||
|
+No message goes out for `decision.seen`, for the raising run's own withdraw, for
|
||||||
|
+a supersede (also the raising run's withdraw), or for a within-role decision,
|
||||||
|
+which closes inside `decision.raise`. `message.receive` gives a human close's
|
||||||
|
+message the close's `human.input` answer event as its `request`, so the role's
|
||||||
|
+turn cites it as it cites a human instruction; a role close's `request` is
|
||||||
|
+`null`. The message also appears in the decision's `trail`.
|
||||||
|
+
|
||||||
|
## Human and reader paths
|
||||||
|
|
||||||
|
`src/human-cli.mjs <socket>` is S4's transport shim, not another product CLI.
|
||||||
|
diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs
|
||||||
|
index f4a39194f..4ab951135 100644
|
||||||
|
--- a/packages/bus/src/broker.mjs
|
||||||
|
+++ b/packages/bus/src/broker.mjs
|
||||||
|
@@ -889,6 +889,30 @@ export class Broker {
|
||||||
|
{ kind: 'answer', decision: d.id, op, choice: a.choice ?? null },
|
||||||
|
d.id,
|
||||||
|
);
|
||||||
|
+ // Row 115 (#1609): a close by anyone but the raising run tells the raising role, routed like message.send's.
|
||||||
|
+ if (op !== 'seen' && (s.human || s.run !== d.raised_by_run)) {
|
||||||
|
+ const outcome =
|
||||||
|
+ op === 'resolved'
|
||||||
|
+ ? `resolved by ${by}: ${a.choice} (${JSON.parse(d.options).find((o) => o.key === a.choice).text})`
|
||||||
|
+ : `withdrawn by ${by}`;
|
||||||
|
+ const mid = randomUUID();
|
||||||
|
+ this.#store.run(
|
||||||
|
+ 'INSERT INTO messages(id,at,business,from_role,from_run,to_role,class,in_reply_to,decision,corrects,body) VALUES(?,?,?,?,?,?,?,?,?,?,?)',
|
||||||
|
+ mid,
|
||||||
|
+ this.#now(),
|
||||||
|
+ s.business,
|
||||||
|
+ s.human ? 'human' : s.role,
|
||||||
|
+ s.human ? 'human-cli' : s.run,
|
||||||
|
+ d.raised_by_role,
|
||||||
|
+ 'DECISION',
|
||||||
|
+ null,
|
||||||
|
+ d.id,
|
||||||
|
+ null,
|
||||||
|
+ `Decision ${d.id} on ${d.action} was ${outcome}.${a.note === undefined ? '' : `\nNote: ${a.note}`}`,
|
||||||
|
+ );
|
||||||
|
+ const h = this.#holder(s.business, d.raised_by_role);
|
||||||
|
+ this.#delivery(mid, 'routed', h?.op === 'claim' ? h : null);
|
||||||
|
+ }
|
||||||
|
return { id: d.id, op };
|
||||||
|
}
|
||||||
|
case 'message.send': {
|
||||||
|
@@ -978,15 +1002,7 @@ export class Broker {
|
||||||
|
s.human || s.tester ? null : this.#holder(s.business, s.role),
|
||||||
|
s.human || s.tester ? 'cli' : 'broker',
|
||||||
|
);
|
||||||
|
- return rows.map((m) => ({
|
||||||
|
- ...m,
|
||||||
|
- request:
|
||||||
|
- this.#store.get(
|
||||||
|
- "SELECT id FROM events WHERE business=? AND kind='human.input' AND subject=? ORDER BY seq LIMIT 1",
|
||||||
|
- s.business,
|
||||||
|
- m.id,
|
||||||
|
- )?.id ?? null,
|
||||||
|
- }));
|
||||||
|
+ return rows.map((m) => ({ ...m, request: this.#request(s.business, m) }));
|
||||||
|
}
|
||||||
|
case 'message.read': {
|
||||||
|
keys(a, ['id'], ['id']);
|
||||||
|
@@ -1292,6 +1308,24 @@ export class Broker {
|
||||||
|
decision,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
+ // A message's originating human.input: a human instruction's own, or for the
|
||||||
|
+ // message a human decision close sends (row 115, #1609), the close's answer.
|
||||||
|
+ // A decision has one close, so the answer is unique.
|
||||||
|
+ #request(business, m) {
|
||||||
|
+ const own = this.#store.get(
|
||||||
|
+ "SELECT id FROM events WHERE business=? AND kind='human.input' AND subject=? ORDER BY seq LIMIT 1",
|
||||||
|
+ business,
|
||||||
|
+ m.id,
|
||||||
|
+ );
|
||||||
|
+ if (own || m.class !== 'DECISION' || m.from_role !== 'human' || !m.decision) return own?.id ?? null;
|
||||||
|
+ return (
|
||||||
|
+ this.#store.get(
|
||||||
|
+ "SELECT id FROM events WHERE business=? AND kind='human.input' AND subject=? AND json_extract(body,'$.kind')='answer' AND json_extract(body,'$.op') IN ('resolved','withdrawn') ORDER BY seq LIMIT 1",
|
||||||
|
+ business,
|
||||||
|
+ m.decision,
|
||||||
|
+ )?.id ?? null
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
// The message a review verb sends, routed like message.send's.
|
||||||
|
#reviewMessage(s, mid, { to, cls, in_reply_to = null, decision = null, body }) {
|
||||||
|
this.#store.run(
|
||||||
|
diff --git a/packages/bus/tests/decision-close.test.mjs b/packages/bus/tests/decision-close.test.mjs
|
||||||
|
new file mode 100644
|
||||||
|
index 000000000..3164d027b
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/packages/bus/tests/decision-close.test.mjs
|
||||||
|
@@ -0,0 +1,192 @@
|
||||||
|
+// Row 115 (#1609): a decision closed by anyone but the raising run sends the
|
||||||
|
+// raising role a DECISION message in the same transaction as the close.
|
||||||
|
+import test from 'node:test';
|
||||||
|
+import assert from 'node:assert/strict';
|
||||||
|
+import { mkdtempSync, rmSync } from 'node:fs';
|
||||||
|
+import { tmpdir } from 'node:os';
|
||||||
|
+import { join } from 'node:path';
|
||||||
|
+import { Store } from '../src/store.mjs';
|
||||||
|
+import { Broker } from '../src/broker.mjs';
|
||||||
|
+
|
||||||
|
+const options = [
|
||||||
|
+ { key: 'yes', text: 'Allow' },
|
||||||
|
+ { key: 'no', text: 'Decline' },
|
||||||
|
+];
|
||||||
|
+const roles = {
|
||||||
|
+ pm: { authority: { withinRole: ['message.send'], crossRole: [] } },
|
||||||
|
+ cto: { authority: { withinRole: ['message.send'], crossRole: [] } },
|
||||||
|
+ coder: { authority: { withinRole: ['message.send'], crossRole: ['task.scope.change'] } },
|
||||||
|
+};
|
||||||
|
+const businesses = { demo: { id: 'demo', human: 'jason', arbiters: { technical: 'cto', delivery: 'pm' }, roles } };
|
||||||
|
+
|
||||||
|
+function setup(t) {
|
||||||
|
+ const root = mkdtempSync(join(tmpdir(), 'bus-decision-close-'));
|
||||||
|
+ const store = new Store(root);
|
||||||
|
+ const b = new Broker({ store, businesses });
|
||||||
|
+ t.after(() => {
|
||||||
|
+ store.close();
|
||||||
|
+ rmSync(root, { recursive: true, force: true });
|
||||||
|
+ });
|
||||||
|
+ const agent = (role, run = `${role}-run`) => {
|
||||||
|
+ const cap = b.bindLaunch({ business: 'demo', role, run, harness: 'pi', address: run });
|
||||||
|
+ b.request(cap, { verb: 'role.claim' });
|
||||||
|
+ return cap;
|
||||||
|
+ };
|
||||||
|
+ const human = b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true });
|
||||||
|
+ const coder = agent('coder');
|
||||||
|
+ const cto = agent('cto');
|
||||||
|
+ const pm = agent('pm');
|
||||||
|
+ // Routed to cto (technical arbiter), to pm (delivery arbiter), and to the human (gated).
|
||||||
|
+ const toCto = raise(b, coder, 'task.scope.change', { domain: 'technical', target: 'vikunja:3/1' });
|
||||||
|
+ const toPm = raise(b, coder, 'task.scope.change', { domain: 'delivery', target: 'vikunja:3/2' });
|
||||||
|
+ const toHuman = raise(b, coder, 'deploy', { target: 'release-1', task_ref: 'vikunja:3/3', blocking: true });
|
||||||
|
+ assert.deepEqual([toCto.route_to, toPm.route_to, toHuman.route_to], ['cto', 'pm', 'human']);
|
||||||
|
+ return { b, store, agent, human, coder, cto, pm, toCto, toPm, toHuman };
|
||||||
|
+}
|
||||||
|
+const call = (b, cap, verb, args = {}) => b.request(cap, { verb, args });
|
||||||
|
+const raise = (b, cap, action, extra = {}) =>
|
||||||
|
+ call(b, cap, 'decision.raise', { action, question: `${action}?`, options, recommendation: 'no', blocking: false, ...extra });
|
||||||
|
+const closes = (store) => store.all("SELECT * FROM messages WHERE class='DECISION' ORDER BY seq");
|
||||||
|
+const deliveries = (store, mid) =>
|
||||||
|
+ store.all('SELECT op,holder_run,transport,address FROM deliveries WHERE message=? ORDER BY seq', mid).map((r) => ({ ...r }));
|
||||||
|
+const answer = (store, decision, op = 'resolved') =>
|
||||||
|
+ store.get(
|
||||||
|
+ "SELECT id FROM events WHERE kind='human.input' AND subject=? AND json_extract(body,'$.kind')='answer' AND json_extract(body,'$.op')=?",
|
||||||
|
+ decision,
|
||||||
|
+ op,
|
||||||
|
+ ).id;
|
||||||
|
+
|
||||||
|
+test('a human resolve sends the raising role one message from the human, and its request is the answer', (t) => {
|
||||||
|
+ const { b, store, human, coder, toHuman } = setup(t);
|
||||||
|
+ call(b, human, 'decision.resolve', { id: toHuman.id, choice: 'no', note: 'not today' });
|
||||||
|
+ const [m, ...rest] = closes(store);
|
||||||
|
+ assert.equal(rest.length, 0);
|
||||||
|
+ const closed = store.get("SELECT by FROM decision_events WHERE decision=? AND op='resolved'", toHuman.id);
|
||||||
|
+ assert.equal(closed.by, 'jason');
|
||||||
|
+ assert.equal(m.business, 'demo');
|
||||||
|
+ assert.equal(m.from_role, 'human');
|
||||||
|
+ assert.equal(m.from_run, 'human-cli');
|
||||||
|
+ assert.equal(m.to_role, 'coder');
|
||||||
|
+ assert.equal(m.class, 'DECISION');
|
||||||
|
+ assert.equal(m.in_reply_to, null);
|
||||||
|
+ assert.equal(m.decision, toHuman.id);
|
||||||
|
+ assert.equal(m.corrects, null);
|
||||||
|
+ assert.equal(m.body, `Decision ${toHuman.id} on deploy was resolved by jason: no (Decline).\nNote: not today`);
|
||||||
|
+ // Routed to the coder's holder, as message.send routes.
|
||||||
|
+ assert.deepEqual(deliveries(store, m.id), [{ op: 'routed', holder_run: 'coder-run', transport: 'pi', address: 'coder-run' }]);
|
||||||
|
+ const [got] = call(b, coder, 'message.receive');
|
||||||
|
+ assert.equal(got.id, m.id);
|
||||||
|
+ assert.equal(got.request, answer(store, toHuman.id));
|
||||||
|
+ // The message joins the decision's trail.
|
||||||
|
+ assert.ok(call(b, human, 'trail', { subject: toHuman.id }).some((r) => r.table === 'messages' && r.id === m.id));
|
||||||
|
+});
|
||||||
|
+
|
||||||
|
+test('a cross-role decision resolved by its arbiter comes from that role and run, with no request', (t) => {
|
||||||
|
+ const { b, store, coder, cto, toCto } = setup(t);
|
||||||
|
+ call(b, cto, 'decision.resolve', { id: toCto.id, choice: 'yes' });
|
||||||
|
+ const [m, ...rest] = closes(store);
|
||||||
|
+ assert.equal(rest.length, 0);
|
||||||
|
+ assert.equal(m.from_role, 'cto');
|
||||||
|
+ assert.equal(m.from_run, 'cto-run');
|
||||||
|
+ assert.equal(m.to_role, 'coder');
|
||||||
|
+ assert.equal(m.decision, toCto.id);
|
||||||
|
+ // No note, so no Note line; by is the arbiter's run.
|
||||||
|
+ assert.equal(m.body, `Decision ${toCto.id} on task.scope.change was resolved by cto-run: yes (Allow).`);
|
||||||
|
+ const [got] = call(b, coder, 'message.receive');
|
||||||
|
+ assert.equal(got.id, m.id);
|
||||||
|
+ assert.equal(got.request, null);
|
||||||
|
+});
|
||||||
|
+
|
||||||
|
+test('a human withdraw sends one message with the note', (t) => {
|
||||||
|
+ const { b, store, human, coder, toPm } = setup(t);
|
||||||
|
+ call(b, human, 'decision.withdraw', { id: toPm.id, note: 'superseded by the plan\nsee #1609' });
|
||||||
|
+ const [m, ...rest] = closes(store);
|
||||||
|
+ assert.equal(rest.length, 0);
|
||||||
|
+ assert.equal(m.from_role, 'human');
|
||||||
|
+ assert.equal(m.to_role, 'coder');
|
||||||
|
+ assert.equal(m.body, `Decision ${toPm.id} on task.scope.change was withdrawn by jason.\nNote: superseded by the plan\nsee #1609`);
|
||||||
|
+ const [got] = call(b, coder, 'message.receive');
|
||||||
|
+ assert.equal(got.request, answer(store, toPm.id, 'withdrawn'));
|
||||||
|
+});
|
||||||
|
+
|
||||||
|
+test("a human seen before the close leaves the request on the close's answer, not the seen", (t) => {
|
||||||
|
+ const { b, store, human, coder, toHuman } = setup(t);
|
||||||
|
+ const other = raise(b, coder, 'deploy', { target: 'release-2', task_ref: 'vikunja:3/5' });
|
||||||
|
+ assert.equal(other.route_to, 'human');
|
||||||
|
+ // A human seen writes a human.input answer with op 'seen' on the decision too.
|
||||||
|
+ call(b, human, 'decision.seen', { id: toHuman.id });
|
||||||
|
+ call(b, human, 'decision.seen', { id: other.id });
|
||||||
|
+ const seen = answer(store, toHuman.id, 'seen');
|
||||||
|
+ assert.ok(seen);
|
||||||
|
+ call(b, human, 'decision.resolve', { id: toHuman.id, choice: 'yes' });
|
||||||
|
+ call(b, human, 'decision.withdraw', { id: other.id });
|
||||||
|
+ const got = call(b, coder, 'message.receive');
|
||||||
|
+ assert.deepEqual(
|
||||||
|
+ got.map((m) => [m.decision, m.request]),
|
||||||
|
+ [
|
||||||
|
+ [toHuman.id, answer(store, toHuman.id, 'resolved')],
|
||||||
|
+ [other.id, answer(store, other.id, 'withdrawn')],
|
||||||
|
+ ],
|
||||||
|
+ );
|
||||||
|
+ assert.notEqual(got[0].request, seen);
|
||||||
|
+ assert.notEqual(got[1].request, answer(store, other.id, 'seen'));
|
||||||
|
+});
|
||||||
|
+
|
||||||
|
+test("the raising run's withdraw, a supersede, a within-role raise and seen send nothing", (t) => {
|
||||||
|
+ const { b, store, human, coder, cto, toCto, toPm, toHuman } = setup(t);
|
||||||
|
+ call(b, coder, 'decision.withdraw', { id: toPm.id, note: 'not needed' });
|
||||||
|
+ raise(b, coder, 'task.scope.change', { domain: 'technical', target: 'vikunja:3/1', supersedes: toCto.id });
|
||||||
|
+ raise(b, coder, 'message.send', { choice: 'yes' });
|
||||||
|
+ const late = raise(b, coder, 'task.scope.change', { domain: 'technical', target: 'vikunja:3/4' });
|
||||||
|
+ call(b, cto, 'decision.seen', { id: late.id, note: 'looking' });
|
||||||
|
+ call(b, human, 'decision.seen', { id: toHuman.id });
|
||||||
|
+ // Three withdrawals or closes and two seen rows, and no DECISION message.
|
||||||
|
+ assert.equal(store.get("SELECT count(*) AS n FROM decision_events WHERE op IN ('withdrawn','resolved')").n, 3);
|
||||||
|
+ assert.equal(store.get("SELECT count(*) AS n FROM decision_events WHERE op='seen'").n, 2);
|
||||||
|
+ assert.deepEqual(closes(store), []);
|
||||||
|
+ assert.deepEqual(call(b, coder, 'message.receive'), []);
|
||||||
|
+});
|
||||||
|
+
|
||||||
|
+test('refused closes send nothing: invalid-choice, decision-closed, not-resolver', (t) => {
|
||||||
|
+ const { b, store, human, coder, cto, pm, toCto, toPm, toHuman } = setup(t);
|
||||||
|
+ assert.throws(() => call(b, human, 'decision.resolve', { id: toHuman.id, choice: 'maybe' }), /invalid-choice/);
|
||||||
|
+ assert.throws(() => call(b, pm, 'decision.resolve', { id: toCto.id, choice: 'yes' }), /not-resolver/);
|
||||||
|
+ assert.throws(() => call(b, cto, 'decision.withdraw', { id: toCto.id }), /not-resolver/);
|
||||||
|
+ assert.deepEqual(closes(store), []);
|
||||||
|
+ call(b, pm, 'decision.resolve', { id: toPm.id, choice: 'no' });
|
||||||
|
+ assert.equal(closes(store).length, 1);
|
||||||
|
+ assert.throws(() => call(b, pm, 'decision.resolve', { id: toPm.id, choice: 'yes' }), /decision-closed/);
|
||||||
|
+ assert.throws(() => call(b, human, 'decision.withdraw', { id: toPm.id }), /decision-closed/);
|
||||||
|
+ assert.equal(closes(store).length, 1);
|
||||||
|
+ assert.equal(call(b, coder, 'message.receive').length, 1);
|
||||||
|
+});
|
||||||
|
+
|
||||||
|
+test('a raising role nobody holds at the close gets the message on the next claim', (t) => {
|
||||||
|
+ const { b, store, agent, human, coder, toHuman } = setup(t);
|
||||||
|
+ call(b, coder, 'role.release');
|
||||||
|
+ call(b, human, 'decision.resolve', { id: toHuman.id, choice: 'yes' });
|
||||||
|
+ const [m] = closes(store);
|
||||||
|
+ assert.deepEqual(deliveries(store, m.id), [{ op: 'routed', holder_run: null, transport: null, address: null }]);
|
||||||
|
+ const next = agent('coder', 'coder-run-2');
|
||||||
|
+ assert.deepEqual(deliveries(store, m.id).at(-1), { op: 'routed', holder_run: 'coder-run-2', transport: 'pi', address: 'coder-run-2' });
|
||||||
|
+ const [got] = call(b, next, 'message.receive');
|
||||||
|
+ assert.equal(got.id, m.id);
|
||||||
|
+ assert.equal(got.body, `Decision ${toHuman.id} on deploy was resolved by jason: yes (Allow).`);
|
||||||
|
+ assert.equal(got.request, answer(store, toHuman.id));
|
||||||
|
+ assert.equal(deliveries(store, m.id).at(-1).op, 'delivered');
|
||||||
|
+});
|
||||||
|
+
|
||||||
|
+test("a human DECISION send keeps its own instruction as the request, not a close's answer", (t) => {
|
||||||
|
+ const { b, store, human, coder, toHuman } = setup(t);
|
||||||
|
+ call(b, human, 'decision.resolve', { id: toHuman.id, choice: 'no' });
|
||||||
|
+ const sent = call(b, human, 'message.send', { to: 'coder', body: 'about that', class: 'DECISION', decision: toHuman.id });
|
||||||
|
+ const got = call(b, coder, 'message.receive');
|
||||||
|
+ assert.deepEqual(
|
||||||
|
+ got.map((m) => [m.from_role, m.request]),
|
||||||
|
+ [
|
||||||
|
+ ['human', answer(store, toHuman.id)],
|
||||||
|
+ ['human', sent.request],
|
||||||
|
+ ],
|
||||||
|
+ );
|
||||||
|
+ assert.notEqual(sent.request, answer(store, toHuman.id));
|
||||||
|
+});
|
||||||
|
diff --git a/packages/bus/tests/single-use.test.mjs b/packages/bus/tests/single-use.test.mjs
|
||||||
|
index 00aed1d99..65e36292d 100644
|
||||||
|
--- a/packages/bus/tests/single-use.test.mjs
|
||||||
|
+++ b/packages/bus/tests/single-use.test.mjs
|
||||||
|
@@ -216,7 +216,9 @@ test('message.send consumes approval and prevents a later send or authorize', (t
|
||||||
|
/decision-consumed/,
|
||||||
|
);
|
||||||
|
assert.throws(() => f.use(d), /decision-consumed/);
|
||||||
|
- assert.equal(f.store.get('SELECT count(*) AS n FROM messages').n, 1);
|
||||||
|
+ // The approval's close sent the raiser one DECISION message (row 115, #1609); one send got through.
|
||||||
|
+ assert.equal(f.store.get("SELECT count(*) AS n FROM messages WHERE class='DECISION'").n, 1);
|
||||||
|
+ assert.equal(f.store.get("SELECT count(*) AS n FROM messages WHERE class<>'DECISION'").n, 1);
|
||||||
|
const fresh = f.approve('message.send', 'delivery', 'pm');
|
||||||
|
f.use(fresh);
|
||||||
|
assert.throws(
|
||||||
|
diff --git a/packages/harness/tests/runner.test.mjs b/packages/harness/tests/runner.test.mjs
|
||||||
|
index 896e5bafa..29d17af4b 100644
|
||||||
|
--- a/packages/harness/tests/runner.test.mjs
|
||||||
|
+++ b/packages/harness/tests/runner.test.mjs
|
||||||
|
@@ -266,6 +266,42 @@ test("a message read a day after it was sent shows the time it was sent", async
|
||||||
|
assert.ok(Date.parse(reply.at) - Date.parse(sentAt) >= 86_000_000);
|
||||||
|
});
|
||||||
|
|
||||||
|
+// Row 115 (#1609): the human closing the role's decision gives the role a turn. The runner is unchanged;
|
||||||
|
+// the header cites the decision and the close's human.input answer as the request.
|
||||||
|
+test("a decision the human resolves becomes a turn citing the decision and the answer", async (t) => {
|
||||||
|
+ const ctx = await setup(t);
|
||||||
|
+ const r = startRunner(ctx.runDir, ctx.cap);
|
||||||
|
+ t.after(() => r.child.kill("SIGKILL"));
|
||||||
|
+ await until(() => ctx.store.get("SELECT 1 FROM role_claims WHERE role='coder' AND op='claim'"));
|
||||||
|
+ const d = ctx.call(ctx.cap, "decision.raise", {
|
||||||
|
+ action: "deploy",
|
||||||
|
+ target: "release-1",
|
||||||
|
+ question: "Ship release-1?",
|
||||||
|
+ options: [{ key: "yes", text: "Allow" }, { key: "no", text: "Decline" }],
|
||||||
|
+ recommendation: "yes",
|
||||||
|
+ blocking: false,
|
||||||
|
+ });
|
||||||
|
+ assert.equal(d.route_to, "human");
|
||||||
|
+ ctx.call(ctx.human, "decision.resolve", { id: d.id, choice: "yes" });
|
||||||
|
+ const m = ctx.store.get("SELECT id,at FROM messages WHERE class='DECISION' AND decision=?", d.id);
|
||||||
|
+ const answer = ctx.store.get("SELECT id FROM events WHERE kind='human.input' AND subject=?", d.id).id;
|
||||||
|
+ const [reply] = await until(() => {
|
||||||
|
+ const got = ctx.call(ctx.human, "message.receive");
|
||||||
|
+ return got.length ? got : null;
|
||||||
|
+ });
|
||||||
|
+ assert.equal(reply.from_role, "coder");
|
||||||
|
+ assert.equal(reply.class, "RESULT");
|
||||||
|
+ assert.equal(reply.in_reply_to, m.id);
|
||||||
|
+ assert.ok(
|
||||||
|
+ reply.body.endsWith(
|
||||||
|
+ `\nMessage ${m.id} from human, sent ${m.at}, class DECISION, citing decision ${d.id}, request ${answer}:\n\n` +
|
||||||
|
+ `Decision ${d.id} on deploy was resolved by jason: yes (Allow).`,
|
||||||
|
+ ),
|
||||||
|
+ reply.body,
|
||||||
|
+ );
|
||||||
|
+ await until(() => ctx.store.get("SELECT 1 FROM deliveries WHERE message=? AND op='read'", m.id));
|
||||||
|
+});
|
||||||
|
+
|
||||||
|
test("a failed state read fails the turn: no adapter runs, the sender hears why, the message is read", async (t) => {
|
||||||
|
const ctx = await setup(t);
|
||||||
|
ctx.inbox = [];
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
f10e2d5f42e6d74b5be21e7bcabe20fd9e0bd2252aab924a5843e57551d30a38 packages/bus/README.md
|
||||||
|
92a90ffdca975100f9dca9d5bc38a8fc924008a006e6f3b088d1473bbcbb2a3d packages/bus/src/broker.mjs
|
||||||
|
1bdf24d1d28c952526323aa47e19503e89d1e5491f28b61d60cb7b5bebc718c6 packages/bus/tests/decision-close.test.mjs
|
||||||
|
bbc2cbaac792633ae52382f5a557ce11fba483a5031363902d5b3a1bb9fa7095 packages/bus/tests/single-use.test.mjs
|
||||||
|
0c6526ff3898f3f8c0cde14477f0648d705bcc522e9e6f263615cc4e658a462a packages/harness/tests/runner.test.mjs
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
base 94ece491f DOCKER_HOST=unset docker=29.7.2 start 16:54:23
|
||||||
|
pkg-bus rc=0 16:54:23-16:54:27 ℹ pass 156 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 4086.59064
|
||||||
|
pkg-business rc=0 16:54:27-16:54:29 ℹ pass 67 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 1770.198703
|
||||||
|
pkg-cli rc=0 16:54:29-16:54:47 ℹ pass 184 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 18581.274006
|
||||||
|
pkg-control-board rc=0 16:54:47-16:54:48 ℹ pass 124 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 711.46664
|
||||||
|
pkg-conversation rc=0 16:54:48-16:55:36 ℹ pass 182 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 47558.010294
|
||||||
|
pkg-discord rc=0 16:55:36-16:55:38 ℹ pass 178 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 2603.207095
|
||||||
|
pkg-harness rc=0 16:55:38-16:55:50 ℹ pass 81 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 11322.551241
|
||||||
|
pkg-ledger rc=0 16:55:50-16:56:12 ℹ pass 86 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 22378.997266
|
||||||
|
pkg-mosaic rc=0 16:56:12-16:56:14 ℹ pass 69 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 2079.298271
|
||||||
|
pkg-queue rc=0 16:56:14-16:56:44 ℹ pass 156 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 29876.591312
|
||||||
|
pkg-runs rc=0 16:56:44-16:56:45 ℹ pass 43 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 785.363241
|
||||||
|
pkg-seat rc=0 16:56:45-16:56:50 ℹ pass 120 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 4896.209811
|
||||||
|
pkg-tasks rc=0 16:56:50-16:56:55 ℹ pass 92 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 5381.346732
|
||||||
|
pkg-webui rc=0 16:56:55-16:57:50 ℹ pass 108 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 54610.2356
|
||||||
|
script-auth rc=0 16:57:50-16:57:50 | selftest: 15 passed, 0 failed
|
||||||
|
script-conductor rc=0 16:57:50-16:57:56 | selftest: 17 passed, 0 failed
|
||||||
|
script-config rc=0 16:57:56-16:57:57 | selftest: 24 passed, 0 failed
|
||||||
|
script-discord rc=0 16:57:57-16:58:04 | discord suite: 66 passed, 0 failed
|
||||||
|
script-extension-package rc=0 16:58:04-16:58:05 | extension package selftest: 18 passed, 0 failed
|
||||||
|
script-foundation rc=0 16:58:05-16:58:41 | selftest: 44 passed, 0 failed
|
||||||
|
script-queue rc=0 16:58:41-16:59:12 ℹ pass 156 ℹ fail 0 | queue suite: 28 passed, 0 failed
|
||||||
|
script-release rc=0 16:59:12-16:59:25 | selftest: 14 passed, 0 failed
|
||||||
|
script-task rc=0 16:59:25-17:00:04 | selftest: 98 passed, 0 failed
|
||||||
|
GATE_PASSED 17:00:04
|
||||||
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -o pipefail
|
||||||
|
# Darkwing gate, row 115 (#1609), candidate in the r115 worktree on base 94ece491f.
|
||||||
|
# Every package's node suite, then every scripts/test-*.sh, sequential, each
|
||||||
|
# teed to its own log; stops at the first failure.
|
||||||
|
G=$HOME/darkwing-scratch/r115/wt
|
||||||
|
OUT=$HOME/darkwing-scratch/r115/gate
|
||||||
|
export TMPDIR=$HOME/darkwing-scratch/r115/tmp
|
||||||
|
mkdir -p "$OUT" "$TMPDIR"
|
||||||
|
cd "$G" || exit 9
|
||||||
|
[ -e "$G/node_modules" ] || { echo "STOP no node_modules in $G"; exit 8; }
|
||||||
|
echo "base $(git rev-parse --short HEAD) DOCKER_HOST=${DOCKER_HOST:-unset} docker=$(docker info --format '{{.ServerVersion}}' 2>/dev/null || echo none) start $(date -u +%H:%M:%S)"
|
||||||
|
run() { local name="$1" dir="$2" cmd="$3" t0; t0=$(date -u +%H:%M:%S)
|
||||||
|
(cd "$G/$dir" && bash -c "$cmd") 2>&1 | tee "$OUT/$name.log" > /dev/null; local rc=$?
|
||||||
|
echo "$name rc=$rc $t0-$(date -u +%H:%M:%S) $(grep -E '^ℹ (pass|fail|skipped) [0-9]+' "$OUT/$name.log" | tr '\n' ' ') | $(tail -n1 "$OUT/$name.log")"
|
||||||
|
[ $rc -eq 0 ] || { echo "STOP $name"; exit $rc; }; }
|
||||||
|
for d in packages/*/; do p=$(basename "$d"); [ -d "$d/tests" ] || continue
|
||||||
|
run "pkg-$p" "packages/$p" 'node --test tests/*.test.mjs'; done
|
||||||
|
for s in scripts/test-*.sh; do n=$(basename "$s" .sh); run "script-${n#test-}" . "bash $s"; done
|
||||||
|
echo "GATE_PASSED $(date -u +%H:%M:%S)"
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (116.430954ms)
|
||||||
|
✔ agents: working counts what the holder received and has not read; routed alone is not working (197.998144ms)
|
||||||
|
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (331.157873ms)
|
||||||
|
✔ agents: working wins over waiting, and Input needed still shows (171.532702ms)
|
||||||
|
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (207.621011ms)
|
||||||
|
✔ agents: state reads only its own business, even for a run name both use (193.807957ms)
|
||||||
|
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (248.913861ms)
|
||||||
|
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (205.119784ms)
|
||||||
|
✔ agents: stranded is oldest first and capped at 50 for a role (842.630195ms)
|
||||||
|
✔ agents: stranded is per role and per business (299.384727ms)
|
||||||
|
✔ agents: a message routed to a held role and not yet received is not stranded (136.222535ms)
|
||||||
|
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (142.721285ms)
|
||||||
|
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (255.172191ms)
|
||||||
|
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (274.432622ms)
|
||||||
|
✔ launch events require a human CLI capability; generic emit cannot forge authority events (176.573964ms)
|
||||||
|
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (172.000057ms)
|
||||||
|
✔ a recommendation that is not exactly one option key refuses invalid-recommendation after the options (269.233149ms)
|
||||||
|
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (215.496033ms)
|
||||||
|
✔ task action subjects and linked decision trail are complete and ordered (198.17611ms)
|
||||||
|
✔ launch binding is durable and reconnecting requires the identical trusted record (119.583146ms)
|
||||||
|
✔ business isolation includes inherited object names and cross-business message references (218.328192ms)
|
||||||
|
✔ authority never transfers between action, run, target, unresolved or replaced role holder (264.251986ms)
|
||||||
|
✔ task projection uses schema current view, skipping earlier and equal-start polls (138.698026ms)
|
||||||
|
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (196.667745ms)
|
||||||
|
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (127.998213ms)
|
||||||
|
✔ both arbiters require human resolution when their cross-role route is themselves (215.895459ms)
|
||||||
|
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (128.809305ms)
|
||||||
|
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.926751ms)
|
||||||
|
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (93.728964ms)
|
||||||
|
✔ credentials refuses a role session and any argument (161.723657ms)
|
||||||
|
✔ credentials reads the status on each request, and a broker without one lists none (218.485352ms)
|
||||||
|
✔ the broker process answers credentials without a token, path or variable in its output, log or store (168.31325ms)
|
||||||
|
✔ only validated broker references load; returned data and exceptions cannot expose a known token (4.249155ms)
|
||||||
|
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (1.821655ms)
|
||||||
|
✔ expiry refuses use and env references never become client data (0.54079ms)
|
||||||
|
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.317594ms)
|
||||||
|
✔ opaque tokens shorter than 16 characters refuse before use (0.318318ms)
|
||||||
|
✔ a human resolve sends the raising role one message from the human, and its request is the answer (219.134195ms)
|
||||||
|
✔ a cross-role decision resolved by its arbiter comes from that role and run, with no request (226.46853ms)
|
||||||
|
✔ a human withdraw sends one message with the note (253.098126ms)
|
||||||
|
✔ a human seen before the close leaves the request on the close's answer, not the seen (278.511844ms)
|
||||||
|
✔ the raising run's withdraw, a supersede, a within-role raise and seen send nothing (287.531746ms)
|
||||||
|
✔ refused closes send nothing: invalid-choice, decision-closed, not-resolver (333.278704ms)
|
||||||
|
✔ a raising role nobody holds at the close gets the message on the next claim (258.207202ms)
|
||||||
|
✔ a human DECISION send keeps its own instruction as the request, not a close's answer (260.057206ms)
|
||||||
|
✔ roles lists open decisions routed to a role, and never the human inbox (202.353509ms)
|
||||||
|
✔ recent lists closed decisions of every route, newest close first, with how each closed (285.621903ms)
|
||||||
|
✔ recent gives the closing event as closed when the decision was seen first (291.951214ms)
|
||||||
|
✔ recent stops at RECENT rows (843.233743ms)
|
||||||
|
✔ every row is what trail already gives the same session for that id (257.379296ms)
|
||||||
|
✔ a role session can't list decisions, so it can't read another role's open decisions (404.428245ms)
|
||||||
|
✔ decisions is a read: one business, strict arguments, nothing written (352.914488ms)
|
||||||
|
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (115.049456ms)
|
||||||
|
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (128.918313ms)
|
||||||
|
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (146.773067ms)
|
||||||
|
✔ endLaunch leaves a claim another run took alone (176.766749ms)
|
||||||
|
✔ refuse records action.refused against the caller with the code only (145.290846ms)
|
||||||
|
✔ launches off refuses role.launch with launch-revoked until launches on (186.845218ms)
|
||||||
|
✔ broker process: launch ops authorize role.launch, record refusals and end runs (265.156167ms)
|
||||||
|
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.243815ms)
|
||||||
|
✔ process reader gets own kernel identity without exposing environment values (0.568432ms)
|
||||||
|
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.634815ms)
|
||||||
|
✔ real pid 1 remains inspectable when its environment is protected (0.281504ms)
|
||||||
|
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (182.965732ms)
|
||||||
|
✔ missing and foreign-business citations refuse and roll back message and grant (181.505734ms)
|
||||||
|
✔ cross-role sends still need a matching resolved decision and consume it once (247.272146ms)
|
||||||
|
✔ messages pages all human messages newest first without writes; read state is history (2551.046155ms)
|
||||||
|
✔ messages refuses role callers, invalid paging and cursors outside this human feed (227.285019ms)
|
||||||
|
✔ views messages passes only its paging arguments through (0.245843ms)
|
||||||
|
✔ messages isolates businesses even with a valid foreign cursor (161.256762ms)
|
||||||
|
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (188.621409ms)
|
||||||
|
✔ startup token refusal returns safe code without value or partial listening broker (38.807924ms)
|
||||||
|
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (180.711708ms)
|
||||||
|
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (169.621918ms)
|
||||||
|
✔ trusted host registers later launches; socket clients never have a registration verb (184.700561ms)
|
||||||
|
✔ runtime excludes declared project roots even when host supplies no repoRoots (37.214897ms)
|
||||||
|
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (164.358197ms)
|
||||||
|
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (222.747662ms)
|
||||||
|
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (197.762912ms)
|
||||||
|
✔ a message with no reply gives an empty list (172.149545ms)
|
||||||
|
✔ replies come oldest first and stop at REPLIES (801.050008ms)
|
||||||
|
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (313.05519ms)
|
||||||
|
✔ strict arguments; a role session gets human-required (366.941895ms)
|
||||||
|
✔ views passes replies through as one call with the id (0.252149ms)
|
||||||
|
✔ requirements returns the committed list to a holding role session, and no one else (179.375886ms)
|
||||||
|
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (150.907889ms)
|
||||||
|
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (180.807621ms)
|
||||||
|
✔ a push is recorded as the action.allowed for git.push.working on the task, and only the host records one (218.332749ms)
|
||||||
|
✔ review.request names the latest pushed commit and sends the reviewer a REVIEW-REQUEST (250.46995ms)
|
||||||
|
✔ review.request says which run holds the reviewer role, or null, and a null request waits for a claim (296.952084ms)
|
||||||
|
✔ review.request refuses a commit that isn't the latest push, a non-reviewer and a role without the action (382.22745ms)
|
||||||
|
✔ reviewFetch answers only the addressed reviewer, for the request it cites (277.172843ms)
|
||||||
|
✔ review.verdict answers one request once, to the role that asked (304.450195ms)
|
||||||
|
✔ task.close on a pushed task needs an approve verdict for the latest push (381.8102ms)
|
||||||
|
✔ a refusal the host decided is filed on the task it names, and only a task ref counts (234.185625ms)
|
||||||
|
✔ v3b prototype refusals, views and append-only mutations (1049.824213ms)
|
||||||
|
✔ gated approval authorizes once, survives store reopen, and fresh approval works (244.59359ms)
|
||||||
|
✔ another run cannot consume an approval; a failed check leaves it usable (230.977121ms)
|
||||||
|
✔ two scheduled callers have exactly one grant and one consumed refusal (173.658076ms)
|
||||||
|
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (328.698933ms)
|
||||||
|
✔ class drift gated to cross-role refuses before consumption (243.495076ms)
|
||||||
|
✔ class drift cross-role to gated refuses before consumption (302.575724ms)
|
||||||
|
✔ class drift gated to within-role refuses before consumption (230.116681ms)
|
||||||
|
✔ class drift cross-role to within-role refuses before consumption (256.194935ms)
|
||||||
|
✔ class drift within-role to gated refuses before consumption (208.191395ms)
|
||||||
|
✔ class drift within-role to cross-role refuses before consumption (179.581178ms)
|
||||||
|
✔ message.send consumes approval and prevents a later send or authorize (199.990898ms)
|
||||||
|
✔ role.revoke consumes approval and prevents a later revoke or authorize (223.774655ms)
|
||||||
|
✔ a business that never switched launches reads allowed with no time and no event (126.725109ms)
|
||||||
|
✔ launches revoked and then restored read allowed, with the restore time and event id (153.309543ms)
|
||||||
|
✔ a task closed on the tracker leaves the open list, and total counts the open ones (163.781307ms)
|
||||||
|
✔ a tombstone is not open (142.499834ms)
|
||||||
|
✔ the open list stops at the cap and says so; total still counts every open task (162.081697ms)
|
||||||
|
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (140.634292ms)
|
||||||
|
✔ a human session reads the same state, with the same cap (149.331121ms)
|
||||||
|
✔ reading state writes nothing: every table keeps its row count (155.796334ms)
|
||||||
|
✔ creates private WAL store and excludes a second writer until explicit close (139.900672ms)
|
||||||
|
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (181.749362ms)
|
||||||
|
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (188.846841ms)
|
||||||
|
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (172.384008ms)
|
||||||
|
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (139.850738ms)
|
||||||
|
✔ async transactions refuse before invoking their function (101.964738ms)
|
||||||
|
✔ the session default fits under the reply cap and pages to the end with next (290.777864ms)
|
||||||
|
✔ the short form: no description, assignees by role, every listed key (255.009921ms)
|
||||||
|
✔ each filter, alone and together, and total counts every match (363.34398ms)
|
||||||
|
✔ task_ref reads one task in full, as the whole board has it (283.482386ms)
|
||||||
|
✔ no arguments is the read the Console and mosaic make, unchanged (260.141681ms)
|
||||||
|
✔ an unknown argument or a bad value refuses invalid-request (856.49545ms)
|
||||||
|
✔ tasks rows carry priority and requirement for every session that reads tasks (312.977522ms)
|
||||||
|
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (337.006024ms)
|
||||||
|
✔ an empty business and one with snapshots but no task.created read cleanly (168.705501ms)
|
||||||
|
✔ recordTask keeps sync reads and a role write apart (178.001106ms)
|
||||||
|
✔ read_at must be one canonical UTC format, so the projection compares strings safely (109.397886ms)
|
||||||
|
✔ a bad entry refuses the whole record (154.654923ms)
|
||||||
|
✔ taskView reads the projection for one business (129.546117ms)
|
||||||
|
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (237.033093ms)
|
||||||
|
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (432.549488ms)
|
||||||
|
✔ without an adapter the server refuses every task verb (193.896866ms)
|
||||||
|
✔ the runtime refuses an invalid adapter and closes a valid one (179.251948ms)
|
||||||
|
✔ the process loads the S3 adapter from plain-data trackers (238.432928ms)
|
||||||
|
✔ the tester binds only when the business sets bus.tester to exactly true (855.540289ms)
|
||||||
|
✔ a business may not name a role tester, so the tester address is never a role (228.080648ms)
|
||||||
|
✔ every verb outside the tester list refuses tester-refused and is recorded against the tester (495.414351ms)
|
||||||
|
✔ a tester message is from tester, starts [mos test], and writes no human.input or authority (199.899074ms)
|
||||||
|
✔ a role answers a tester only in reply to a tester message, and the answer never reaches the human (216.785646ms)
|
||||||
|
✔ the tester reads the human views a reader reads (96.209412ms)
|
||||||
|
✔ tester proof binds its own CLI, start time and nonce, refuses a managed run and allows an agent (1.172354ms)
|
||||||
|
✔ neither CLI's nonce proves the other: each proof is bound to its own CLI and nonce variable (0.375666ms)
|
||||||
|
✔ testerNonce reads only the tester's variable and gives null on any read error (0.198664ms)
|
||||||
|
✔ an envelope carries exactly one of cap, human and tester; no tester authenticator refuses (53.124709ms)
|
||||||
|
✔ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (727.195221ms)
|
||||||
|
✔ socket capability stamps launch identity; shared views use wire, no SQL client (176.532223ms)
|
||||||
|
✔ two wire claims serialize; a lost reply never automatically retries (199.301401ms)
|
||||||
|
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (112.198928ms)
|
||||||
|
✔ client preserves UTF-8 when a response divides a multibyte character (14.187275ms)
|
||||||
|
✔ committed mutation followed by dropped reply reports unknown and is never retried (122.466386ms)
|
||||||
|
ℹ tests 156
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 156
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 4086.59064
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
✔ config directory and file path follow MOSAIC_CONFIG (1.10898ms)
|
||||||
|
✔ the fixture business validates and comes back frozen (3.130475ms)
|
||||||
|
✔ two instances may share a definition (1.042164ms)
|
||||||
|
✔ top-level refusals (3.838592ms)
|
||||||
|
✔ arbiters and projects (5.710917ms)
|
||||||
|
✔ role instances (3.270966ms)
|
||||||
|
✔ Vikunja bots (4.871483ms)
|
||||||
|
✔ a role without Vikunja takes no tracker block (1.700733ms)
|
||||||
|
✔ credential references match the definition's services (2.046884ms)
|
||||||
|
✔ launch (6.108159ms)
|
||||||
|
✔ loadBusiness: file checks (1.685704ms)
|
||||||
|
✔ loadBusiness: not a regular file (34.445958ms)
|
||||||
|
✔ loading writes nothing (0.972845ms)
|
||||||
|
✔ names that are Object.prototype properties don't count as declared (2.527616ms)
|
||||||
|
✔ the shipped example refuses as written and validates once filled in (0.522838ms)
|
||||||
|
✔ the role file loads and resolves through loadBusiness (4.383764ms)
|
||||||
|
✔ the shipped file is exactly decision 104's list (0.562222ms)
|
||||||
|
✔ the resolved record matches decision 104, so a widened file fails (1.309836ms)
|
||||||
|
✔ message.send is within role and every other action is gated (1.051315ms)
|
||||||
|
✔ a business grant of a gated action through limits does not widen the role (1.163488ms)
|
||||||
|
✔ a launch block whose by is the chief-of-staff instance is refused (1.531542ms)
|
||||||
|
✔ the instance takes no credential references and no tracker (2.397808ms)
|
||||||
|
✔ usage errors exit 4 (256.8687ms)
|
||||||
|
✔ validate: a good business exits 0 and prints instance digests (64.47629ms)
|
||||||
|
✔ validate: project files (306.251612ms)
|
||||||
|
✔ validate: missing files and a broken system config (222.780557ms)
|
||||||
|
✔ validate: credential reference problems exit 2 and name each one (63.170742ms)
|
||||||
|
✔ validate: a token file inside the repository is refused (60.864785ms)
|
||||||
|
✔ validate: role definitions come from MOSAIC_ROLES_DIR (187.421008ms)
|
||||||
|
✔ resolve: prints one instance's record (184.957202ms)
|
||||||
|
✔ resolve: refusals (364.774864ms)
|
||||||
|
✔ parse: exactly one of file or env, plus the service's date (1.912689ms)
|
||||||
|
✔ check: a good file has no problems (0.620712ms)
|
||||||
|
✔ check never opens the file: a write-only token passes (0.281508ms)
|
||||||
|
✔ check: file problems (0.637931ms)
|
||||||
|
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (0.714025ms)
|
||||||
|
✔ check: dates and environment references (0.313828ms)
|
||||||
|
✔ path and load (1.691793ms)
|
||||||
|
✔ refusals (0.893547ms)
|
||||||
|
✔ systemVars flattens the validated config (1.526445ms)
|
||||||
|
✔ precedence: system, business, project, project role, agent (3.638393ms)
|
||||||
|
✔ limits narrow the definition and never widen it (1.618946ms)
|
||||||
|
✔ role.launch stays within-role only for the instance the launch block names (3.24916ms)
|
||||||
|
✔ limits.authority without role.launch leaves the launcher with no launch block (1.309275ms)
|
||||||
|
✔ limits.authority narrows cross-role actions too (0.761206ms)
|
||||||
|
✔ classify (0.856221ms)
|
||||||
|
✔ the record carries what the broker and launcher need (0.718915ms)
|
||||||
|
✔ digest: key order doesn't matter, any value change does (6.538351ms)
|
||||||
|
✔ refusals (1.783126ms)
|
||||||
|
✔ the four shipped version 2 roles load (2.169322ms)
|
||||||
|
✔ shipped role scopes match addendum B section 2 and the SR runbook (0.678807ms)
|
||||||
|
✔ shipped authority follows the note's table (0.386338ms)
|
||||||
|
✔ version 1 files keep loading with no authority (0.706235ms)
|
||||||
|
✔ the conductor policy isn't a role (0.165825ms)
|
||||||
|
✔ a missing role file is exit 4, a symbolic link too (0.299211ms)
|
||||||
|
✔ version 2 refusals (0.968934ms)
|
||||||
|
✔ authority: closed vocabulary, no gated-only action, no overlap (1.530087ms)
|
||||||
|
✔ credentials: Gitea scopes (0.62378ms)
|
||||||
|
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (0.960543ms)
|
||||||
|
✔ credentials: services (0.465873ms)
|
||||||
|
✔ contract: a non-empty regular Markdown file beside the role file (0.50672ms)
|
||||||
|
✔ every key names known layers and a merge rule (0.977935ms)
|
||||||
|
✔ unknown keys and wrong layers refuse (0.634292ms)
|
||||||
|
✔ types (1.553271ms)
|
||||||
|
✔ merge: defaults, then the most specific layer wins (0.244341ms)
|
||||||
|
✔ merge: limits only narrow, and provenance lists each source (0.289662ms)
|
||||||
|
✔ merge doesn't change its inputs (0.109043ms)
|
||||||
|
ℹ tests 67
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 67
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 1770.198703
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (1285.573733ms)
|
||||||
|
✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (62.818379ms)
|
||||||
|
✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (167.22146ms)
|
||||||
|
✔ NO_COLOR: no escape codes, except reverse video on the selected row (254.241118ms)
|
||||||
|
✔ 1-9 shows the decide flow's own lines and question; only y resolves (156.225226ms)
|
||||||
|
✔ the app shows the same decide text as mosaic decide (102.948209ms)
|
||||||
|
✔ any key but y or Y at the question answers No; nothing is sent (2348.597479ms)
|
||||||
|
✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (474.601629ms)
|
||||||
|
✔ under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10) (1715.366521ms)
|
||||||
|
✔ a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12) (260.010356ms)
|
||||||
|
✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (117.679981ms)
|
||||||
|
✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (760.270793ms)
|
||||||
|
✔ quitting at the question answers No and sends nothing, armed or not (203.649232ms)
|
||||||
|
✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (104.071366ms)
|
||||||
|
✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (146.132975ms)
|
||||||
|
✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (980.862562ms)
|
||||||
|
✔ at every size the decide box shows each line y depends on, or says it is hidden (8372.883567ms)
|
||||||
|
✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (153.075251ms)
|
||||||
|
✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (118.44586ms)
|
||||||
|
✔ help or a detail box cut to fit keeps its last line, Esc included (Dewey) (56.037527ms)
|
||||||
|
✔ keys: views, moving, trail of a selection, the command line, help and quit (223.468805ms)
|
||||||
|
✔ one option: the status line says Press 1, not 1-1 (94.970936ms)
|
||||||
|
✔ no business: the app says so and :business picks one (48.600795ms)
|
||||||
|
✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (1.706122ms)
|
||||||
|
✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (165.890361ms)
|
||||||
|
✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.442471ms)
|
||||||
|
✔ decide with --note still asks; a No sends nothing (53.029669ms)
|
||||||
|
✔ the app's business comes from a live bus host only (1.003679ms)
|
||||||
|
✔ runApp leaves the terminal as it found it on q, end of input, SIGTERM, SIGINT, SIGHUP and an error, the first frame's included (6.982127ms)
|
||||||
|
✔ draw is pure: same state, same frame (0.744611ms)
|
||||||
|
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (127.899428ms)
|
||||||
|
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (127.410634ms)
|
||||||
|
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (110.769459ms)
|
||||||
|
✔ decide prints a declining choice as declining (132.897347ms)
|
||||||
|
✔ an unknown outcome is reported once and never resent (97.907907ms)
|
||||||
|
✔ a decision closed before the answer arrives exits 2 and points at its trail (103.26111ms)
|
||||||
|
✔ a prefix that matches two open decisions exits 2 and resolves neither (102.262142ms)
|
||||||
|
✔ without --business a command uses the live host's business, and a stale host.json is not a host (123.444595ms)
|
||||||
|
✔ every human command refuses inside an agent run before it touches the bus (98.230607ms)
|
||||||
|
✔ usage errors exit 4; no business and no host is a usage error (97.005041ms)
|
||||||
|
✔ agents and tasks print through the broker (89.346654ms)
|
||||||
|
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (4.683693ms)
|
||||||
|
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (55.33757ms)
|
||||||
|
✔ trackers come from the tracker.* variables of the one project that names a tracker project (73.61786ms)
|
||||||
|
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (40.657517ms)
|
||||||
|
✔ two projects that each name a tracker project refuse, since the boot shape holds one (33.546877ms)
|
||||||
|
✔ a business without tracker.baseUrl gets no trackers entry (29.659707ms)
|
||||||
|
✔ an unknown business and a broken system config refuse with exit 3 (58.65766ms)
|
||||||
|
✔ follow prints an unsolicited message after plain talk returned (195.296308ms)
|
||||||
|
✔ talk --follow continues after reply and prints a later unsolicited message (181.185677ms)
|
||||||
|
✔ inbox lists all unread human messages under decisions, does not deliver or read them (2650.832296ms)
|
||||||
|
✔ follow refuses agent context and bad arguments (366.167573ms)
|
||||||
|
✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (310.97508ms)
|
||||||
|
✔ plain talk leaves the process SIGINT handlers unchanged (354.64992ms)
|
||||||
|
✔ empty views say so (1.040076ms)
|
||||||
|
✔ the trail keeps the broker's order and names a decision's task without its rows (1.344657ms)
|
||||||
|
✔ tasks print the tracker fields the snapshot carries (0.219001ms)
|
||||||
|
✔ the review loop: the coder pushes and asks, the reviewer fetches and approves, and the task's trail holds each step (676.839764ms)
|
||||||
|
✔ refusals: no action, non-fast-forward, an unknown commit or request, and a push for another task moves only that task's ref (674.863081ms)
|
||||||
|
✔ the size cap refuses a push before any byte (314.648867ms)
|
||||||
|
✔ a fetch over the cap refuses before any byte reaches the reviewer (531.995173ms)
|
||||||
|
✔ the socket refuses a request with no capability, an unknown one, and a bad request line (390.594827ms)
|
||||||
|
✔ a rigged clone runs nothing: hooks, fsmonitor, a filter, a gitlink and a symlinked .git/config leave no marker (1408.095766ms)
|
||||||
|
✔ an identity with no role, or of another business, refuses not-a-role and moves no ref (529.146024ms)
|
||||||
|
✔ a review of a project the business no longer has refuses unknown-project (378.474801ms)
|
||||||
|
✔ past maxActive connections the socket refuses git-busy, and a hung-up connection frees its slot (270.299717ms)
|
||||||
|
✔ close() answers host-closing to a connection still sending its request line (662.455343ms)
|
||||||
|
✔ a bundle that trickles in refuses body-timeout at its deadline and frees its slot (860.739186ms)
|
||||||
|
✔ a reviewer gone before its bundle was sent is filed fetch-aborted, not as the host's fault (651.016071ms)
|
||||||
|
✔ a reviewer that hangs up the moment it has the whole bundle was served, not aborted (658.536782ms)
|
||||||
|
✔ send: a close after every byte left is served; one that fails a queued write is fetch-aborted (41.655558ms)
|
||||||
|
✔ a bundle with no bytes for bodyIdle refuses body-timeout long before its deadline (612.964251ms)
|
||||||
|
✔ send: a close once the loop is past its last chunk is served, and a failed write there is fetch-aborted (4.044769ms)
|
||||||
|
✔ letGo waits for an answer to leave for at most its cap, then destroys the socket (301.514484ms)
|
||||||
|
✔ a record that fails and can't be undone refuses rollback-failed, which the host logs (518.867959ms)
|
||||||
|
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (924.565359ms)
|
||||||
|
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (239.283378ms)
|
||||||
|
✔ a second host for the same data root refuses with exit 3 while the first runs (208.096935ms)
|
||||||
|
✔ two bus starts racing for one data root leave one host running; the other refuses that a host already runs (627.286785ms)
|
||||||
|
✔ a host takes over a host.lock its dead host left, and puts back a live claim that replaced it meanwhile (1.221744ms)
|
||||||
|
✔ a host.lock that goes between the check and the read is tried again; one that can't be parsed still refuses as unreadable (0.732599ms)
|
||||||
|
✔ a live host's lock stays, however old the file is (0.493804ms)
|
||||||
|
✔ a host.lock left by a host killed with SIGKILL doesn't stop the next bus start (808.554458ms)
|
||||||
|
✔ while a host boots, a reader sees no host: the human commands still ask for --business, stop and status find none (191.021054ms)
|
||||||
|
✔ a host started before host.lock existed, holding only host.json, still refuses a second start, which leaves no lock (26.449437ms)
|
||||||
|
✔ a broker fork that throws leaves no host.lock and no host.json (40.593836ms)
|
||||||
|
✔ a broker send that throws leaves no host.lock and no host.json (49.262649ms)
|
||||||
|
✔ a broker that misses its boot wait leaves no host.lock and no host.json (31.273521ms)
|
||||||
|
✔ a notifier fork that throws stops the broker and leaves no host.lock and no host.json (458.277344ms)
|
||||||
|
✔ a notifier send that throws stops the broker and leaves no host.lock and no host.json (391.09175ms)
|
||||||
|
✔ 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 (280.185263ms)
|
||||||
|
✔ 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 (154.348689ms)
|
||||||
|
✔ a host.json that can't be written stops both children and leaves no host.lock (261.188572ms)
|
||||||
|
✔ an error event on the broker before it replies fails the start at once, and leaves no host.lock and no live child (29.247998ms)
|
||||||
|
✔ an error event on the notifier before it replies fails the start at once, and leaves no host.lock and no live child (153.82173ms)
|
||||||
|
✔ an error event on the broker while the notifier starts fails the start once the notifier replies (271.484309ms)
|
||||||
|
✔ 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 (578.201569ms)
|
||||||
|
✔ 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 (259.210816ms)
|
||||||
|
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1269.623371ms)
|
||||||
|
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (326.578852ms)
|
||||||
|
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (184.754744ms)
|
||||||
|
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (196.043898ms)
|
||||||
|
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (194.862902ms)
|
||||||
|
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (128.080064ms)
|
||||||
|
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (164.693462ms)
|
||||||
|
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (144.911551ms)
|
||||||
|
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (245.633563ms)
|
||||||
|
✔ watchChildren reports a child that died before it was called, and one that dies later (21.020092ms)
|
||||||
|
✔ bus stop refuses to signal a live pid that is not a bus host (203.120661ms)
|
||||||
|
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (232.694035ms)
|
||||||
|
✔ bus start refuses with exit 3 without a notifier config (97.959408ms)
|
||||||
|
✔ bus start runs until bus stop; status reports it while it runs (791.741803ms)
|
||||||
|
✔ bus-service.sh renders the unit and installs it into a given directory (53.475821ms)
|
||||||
|
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (63.906264ms)
|
||||||
|
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (12.957498ms)
|
||||||
|
✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (8.202037ms)
|
||||||
|
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1370.476111ms)
|
||||||
|
✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (775.220201ms)
|
||||||
|
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or kept and logged (1544.944275ms)
|
||||||
|
✔ a project clone that can't be made refuses checkout-failed, from --pm and from a session, with no run and no clone left (513.409091ms)
|
||||||
|
✔ a runner that stops at once ends its launch with the runner's reason (251.53641ms)
|
||||||
|
✔ a Claude Code launch with no session credential refuses before it starts (167.668163ms)
|
||||||
|
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (239.220431ms)
|
||||||
|
✔ a runner killed from the host ends its launch as killed, not failed (324.659999ms)
|
||||||
|
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (538.532078ms)
|
||||||
|
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1366.358594ms)
|
||||||
|
✔ 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 (805.244989ms)
|
||||||
|
✔ 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 (3665.49833ms)
|
||||||
|
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (111.295757ms)
|
||||||
|
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (134.324944ms)
|
||||||
|
✔ a launch client that never closes its side doesn't hold the host's close (136.42814ms)
|
||||||
|
✔ a runner that ignores SIGTERM is killed when the host closes (1276.555557ms)
|
||||||
|
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (4.813766ms)
|
||||||
|
✔ zoned uses the IANA zone across DST (25.028231ms)
|
||||||
|
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (156.019611ms)
|
||||||
|
✔ two blocking decisions get two DMs with different nonces (111.983866ms)
|
||||||
|
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.206893ms)
|
||||||
|
✔ a failed DM is journaled, backs off, and is retried until it lands (108.743331ms)
|
||||||
|
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (108.32701ms)
|
||||||
|
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (119.527411ms)
|
||||||
|
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (102.971411ms)
|
||||||
|
✔ 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 (161.582094ms)
|
||||||
|
✔ a restart after the second refusal does not send before that refusal's 30 min are up (155.563493ms)
|
||||||
|
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (113.505078ms)
|
||||||
|
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (85.589851ms)
|
||||||
|
✔ an inbox read failure is logged and the next poll retries (0.740423ms)
|
||||||
|
✔ no Discord id reaches the journal or the log (135.644827ms)
|
||||||
|
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (21.030694ms)
|
||||||
|
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (40.676142ms)
|
||||||
|
✔ the journal: a whole file that is one torn line truncates to empty (24.194254ms)
|
||||||
|
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (2.121306ms)
|
||||||
|
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.786182ms)
|
||||||
|
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (4.483044ms)
|
||||||
|
✔ the journal: a symlinked directory refuses and says it is a link (0.393442ms)
|
||||||
|
✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.581108ms)
|
||||||
|
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.440023ms)
|
||||||
|
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.582788ms)
|
||||||
|
✔ digest content stays within Discord's 2000 characters (2.013658ms)
|
||||||
|
✔ runLoop never overlaps ticks and stops after the one in flight (111.550907ms)
|
||||||
|
✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (479.71482ms)
|
||||||
|
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (387.937825ms)
|
||||||
|
✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (444.344931ms)
|
||||||
|
✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (285.735398ms)
|
||||||
|
✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (4026.845192ms)
|
||||||
|
✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (483.265258ms)
|
||||||
|
✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (218.857993ms)
|
||||||
|
✔ install never shows a template name on systemd 261; template drop-ins still refuse (155.770446ms)
|
||||||
|
✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (321.881433ms)
|
||||||
|
✔ talk --tester sends a [mos test] message as tester from inside an agent and prints the role's reply (169.894538ms)
|
||||||
|
✔ talk --tester refuses text without the [mos test] prefix before any call (101.360005ms)
|
||||||
|
✔ the read verbs take --tester inside an agent, and the trail shows the tester as the sender (113.413461ms)
|
||||||
|
✔ decide, stop and launches take no --tester (118.825207ms)
|
||||||
|
✔ the tester transport runs tester-cli.mjs, and the tester's refusals exit 3 (0.372279ms)
|
||||||
|
✔ with no stand-in transport, --tester runs tester-cli.mjs on the data root's socket (0.581689ms)
|
||||||
|
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 (396.098559ms)
|
||||||
|
✔ the transport writes {business, verb, args} to the child and reads its JSON (47.20872ms)
|
||||||
|
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2159.437529ms)
|
||||||
|
✔ busExit and refuseInsideAgent (0.473168ms)
|
||||||
|
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (206.198337ms)
|
||||||
|
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1117.205904ms)
|
||||||
|
✔ a late reply the Console has listed still reaches the next talk (2149.497118ms)
|
||||||
|
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (235.328272ms)
|
||||||
|
✔ launches off and on go to the broker and change the business's launch state (321.386299ms)
|
||||||
|
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (227.844499ms)
|
||||||
|
ℹ tests 184
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 184
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 18581.274006
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (4.147401ms)
|
||||||
|
✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.266272ms)
|
||||||
|
✔ completed smoke replies and ordinary questions are idle, not human blockers (1.258198ms)
|
||||||
|
✔ only an explicit first-line input request makes a finished reply waiting (0.270159ms)
|
||||||
|
✔ tool activity, user text, errors and unfinished turns override attention text (0.218992ms)
|
||||||
|
✔ STOP access failure is unknown, not absence, under a non-root identity (38.802933ms)
|
||||||
|
✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (4.120098ms)
|
||||||
|
✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (2.044876ms)
|
||||||
|
✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (8.586418ms)
|
||||||
|
✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.887743ms)
|
||||||
|
✔ server rescans connector discovery and refuses HTTP reply without transport (36.820463ms)
|
||||||
|
✔ connector session links and linked directories are not read (1.083665ms)
|
||||||
|
✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (2.863871ms)
|
||||||
|
✔ CLI print uses the relaunch notice instead of old current preview (67.179713ms)
|
||||||
|
✔ connector owner and fixed task never inherit a native relaunch notice (2.271683ms)
|
||||||
|
✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.088456ms)
|
||||||
|
✔ loadConfig: missing file throws ConfigError (1.823339ms)
|
||||||
|
✔ loadConfig: invalid JSON throws ConfigError (0.324134ms)
|
||||||
|
✔ loadConfig: missing dataRoot throws ConfigError (0.257482ms)
|
||||||
|
✔ loadConfig: relative dataRoot throws ConfigError (0.345798ms)
|
||||||
|
✔ loadConfig: valid config returns dataRoot (0.659441ms)
|
||||||
|
✔ findNewestSession: picks the newest by mtime among two files (0.520594ms)
|
||||||
|
✔ findNewestSession: finds files in nested subdirectories (0.329531ms)
|
||||||
|
✔ findNewestSession: returns null for a missing dir (0.137445ms)
|
||||||
|
✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.698257ms)
|
||||||
|
✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.340001ms)
|
||||||
|
✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.504045ms)
|
||||||
|
✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (1.18582ms)
|
||||||
|
✔ deriveState: full state table (0.181489ms)
|
||||||
|
✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.351002ms)
|
||||||
|
✔ rule: newest entry is a tool result with no assistant text after it is working (0.29795ms)
|
||||||
|
✔ rule: a finished ordinary turn is idle, even if it says your move (0.282085ms)
|
||||||
|
✔ task: the first user message of the session, from text blocks (0.273069ms)
|
||||||
|
✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.277295ms)
|
||||||
|
✔ task: no user message in the log means null (shown as unknown), never a guess (0.305344ms)
|
||||||
|
✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.426763ms)
|
||||||
|
✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.569787ms)
|
||||||
|
✔ scan: the written record carries task, workspace and activeProject (0.521946ms)
|
||||||
|
✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.619398ms)
|
||||||
|
✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.395976ms)
|
||||||
|
✔ registration: a record whose pid is gone is stale; derived values win, sources say derived, registered stays with alive false; a pid the probe cannot decide is not stale; pidAlive itself (1.042136ms)
|
||||||
|
✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.473855ms)
|
||||||
|
✔ loadRegistrations: a missing seatsDir gives empty lists (0.15813ms)
|
||||||
|
✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (0.75516ms)
|
||||||
|
✔ matchRegistration: matches by sessionsDir, and by realpath through a symlink; sessionsDir null never matches; same seat name with a different sessionsDir does not match (fleet vs repo darkwing) (0.332633ms)
|
||||||
|
✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (1.434605ms)
|
||||||
|
✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.280892ms)
|
||||||
|
✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.521144ms)
|
||||||
|
✔ scanAgent: ageSeconds is computed from the injected now (0.297784ms)
|
||||||
|
✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.160514ms)
|
||||||
|
✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.38821ms)
|
||||||
|
✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.398264ms)
|
||||||
|
✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (0.901234ms)
|
||||||
|
✔ scan: relative boardDir throws ConfigError (0.087375ms)
|
||||||
|
✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (63.508107ms)
|
||||||
|
✔ CLI: missing config exits 2 with a refused: message (57.556225ms)
|
||||||
|
✔ CLI: unknown command exits 2 (76.445179ms)
|
||||||
|
✔ CLI: unknown --liveness value exits 2 (81.050548ms)
|
||||||
|
✔ panesRunPi: true when any trimmed line equals 'pi' (0.363523ms)
|
||||||
|
✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.13512ms)
|
||||||
|
✔ tmuxIsAlive: a pane running pi is alive (0.257674ms)
|
||||||
|
✔ tmuxIsAlive: session exists but pi has exited is not alive (0.105288ms)
|
||||||
|
✔ tmuxIsAlive: no such tmux session is not alive (0.080407ms)
|
||||||
|
✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.089065ms)
|
||||||
|
✔ tmuxIsAlive: passes -L <socket> only when a socket is given (0.136831ms)
|
||||||
|
✔ parsePanes: one pane per line, command and optional tab-separated path (0.142941ms)
|
||||||
|
✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.105377ms)
|
||||||
|
✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.138ms)
|
||||||
|
✔ loadSeen: missing file returns {} (0.216084ms)
|
||||||
|
✔ loadSeen: invalid JSON throws ConfigError (0.283112ms)
|
||||||
|
✔ loadSeen: a JSON array throws ConfigError (0.248575ms)
|
||||||
|
✔ loadSeen: a non-string value throws ConfigError (0.259279ms)
|
||||||
|
✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.47413ms)
|
||||||
|
✔ markSeen: seen false deletes the key (0.385301ms)
|
||||||
|
✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.34508ms)
|
||||||
|
✔ markSeen: project containing '/' throws ConfigError (0.200834ms)
|
||||||
|
✔ markSeen: non-boolean seen throws ConfigError (0.170931ms)
|
||||||
|
✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.441446ms)
|
||||||
|
✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.376429ms)
|
||||||
|
✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.369026ms)
|
||||||
|
✔ scanAgent: an error-state session with a matching mark is seen (0.353335ms)
|
||||||
|
✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.744959ms)
|
||||||
|
✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.304874ms)
|
||||||
|
✔ taskSetBy: a registered task carries the record's setter; a record without the field (pre-#1511) reads unknown; the value is not copied into registered (0.973124ms)
|
||||||
|
✔ taskSetBy: null whenever the task shown is not the registered one: no registration, an empty registered task, a stale registration; the field is always present (0.698731ms)
|
||||||
|
✔ taskSetBy: scan() reads the field from disk through the seat package (bounded there), writes it to the per-agent record and index, and an invalid on-disk value is a registrationError, never a row value (1.563692ms)
|
||||||
|
✔ isLoopbackHost: recognizes loopback hosts (1.282256ms)
|
||||||
|
✔ isLoopbackHost: rejects non-loopback hosts (4.635924ms)
|
||||||
|
✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (3.803121ms)
|
||||||
|
✔ startServer: serves page, healthz, and a rescanning /api/board (36.641718ms)
|
||||||
|
✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (6.717329ms)
|
||||||
|
✔ startServer: /api/board returns 500 JSON with an error field when scan throws (2.361524ms)
|
||||||
|
✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (56.104086ms)
|
||||||
|
✔ CLI: serve rejects a non-numeric --port with exit 2 (76.532986ms)
|
||||||
|
✔ CLI: scan still works after the async cli refactor (90.051315ms)
|
||||||
|
✔ CLI: live serve prints its URL and answers /healthz (65.266304ms)
|
||||||
|
✔ page.html: esc() escapes every HTML-significant character (0.693047ms)
|
||||||
|
✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (7.490836ms)
|
||||||
|
✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (3.156159ms)
|
||||||
|
✔ POST /api/seen with invalid JSON returns 400 (1.736641ms)
|
||||||
|
✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (2.062482ms)
|
||||||
|
✔ POST /api/seen with a missing agent returns 400 (1.338777ms)
|
||||||
|
✔ POST /api/board returns 405; PUT /api/seen returns 405 (1.83868ms)
|
||||||
|
✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (56.103224ms)
|
||||||
|
✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.443287ms)
|
||||||
|
✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.366579ms)
|
||||||
|
✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.213564ms)
|
||||||
|
✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.179042ms)
|
||||||
|
✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.321707ms)
|
||||||
|
✔ page.html: task and active project cells show their source via sourceTag(); the detail has a Registered row via registeredText(); SOURCE_LABEL maps registration to registered; every dynamic value in sourceTag/fromSource/registeredText is escaped (0.66808ms)
|
||||||
|
✔ POST /api/reply: runs agent-send.sh with -s from the registration, -S <host>:control-board, -m text plus the fixed trailer, no -L on the default socket, MOSAIC_TMUX_SOCKET stripped; answers delivered with the exit code and both streams (29.120666ms)
|
||||||
|
✔ POST /api/reply: a registration with a tmux socket adds -L <socket> (25.704702ms)
|
||||||
|
✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (23.956114ms)
|
||||||
|
✔ POST /api/reply: refusals before the tool runs: empty or blank or long text 400, unknown row 404, no registration 409, stale registration 409, no tmux session 409, bad JSON 400; the tool is never called (13.632058ms)
|
||||||
|
✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (4.716155ms)
|
||||||
|
✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.16916ms)
|
||||||
|
✔ page.html: the reply box appears only where canReply() holds (live registration with a tmux session), the detail has a Reply row, the submit posts JSON to /api/reply, receipts and drafts survive a refresh, and every receipt value is escaped (0.617004ms)
|
||||||
|
✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (2.43356ms)
|
||||||
|
✔ page.html: the task cell and detail show who set a registered task via setByTag()/setByText(), both escaped, only from rec.taskSetBy; the reply gate does not read it (0.291665ms)
|
||||||
|
✔ Host/Origin guard: GET /api/board and POST /api/reply refuse a foreign Host, a wrong port and a cross-origin Origin with 403 JSON, before any scan or send, and never send CORS headers (7.717488ms)
|
||||||
|
✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (37.827441ms)
|
||||||
|
✔ conversation routes (F16): a foreign Host, a wrong port and a cross-origin Origin get 403 before the reader runs, with no CORS headers (4.31037ms)
|
||||||
|
✔ every refusal code the reader can raise has an HTTP status (0.881168ms)
|
||||||
|
✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (46.900568ms)
|
||||||
|
ℹ tests 124
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 124
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 711.46664
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
✔ W1: two processes acquire the same pair at once; exactly one claim (207.259503ms)
|
||||||
|
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (32.403063ms)
|
||||||
|
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (18.859619ms)
|
||||||
|
✔ W2: acquire while a claim is reserved or active refuses already-active (356.024648ms)
|
||||||
|
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (393.336869ms)
|
||||||
|
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (252.94142ms)
|
||||||
|
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (194.562835ms)
|
||||||
|
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (198.018852ms)
|
||||||
|
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.818116ms)
|
||||||
|
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9908.298292ms)
|
||||||
|
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (31513.721279ms)
|
||||||
|
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (255.258733ms)
|
||||||
|
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (143.75118ms)
|
||||||
|
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (415.138413ms)
|
||||||
|
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (279.171565ms)
|
||||||
|
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (340.08422ms)
|
||||||
|
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (108.951231ms)
|
||||||
|
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (174.111991ms)
|
||||||
|
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (259.755827ms)
|
||||||
|
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (811.487345ms)
|
||||||
|
✔ W11: the controller writes no session file; only the fake engine's own appends appear (187.293433ms)
|
||||||
|
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (80.468371ms)
|
||||||
|
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (73.065293ms)
|
||||||
|
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.949441ms)
|
||||||
|
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.052437ms)
|
||||||
|
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.897569ms)
|
||||||
|
✔ no shim from this file's tests is left running for the after hook (4.964286ms)
|
||||||
|
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (585.041886ms)
|
||||||
|
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (443.007484ms)
|
||||||
|
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (548.145668ms)
|
||||||
|
✔ K4: two engines; force stop one; the other survives by independent observation (2752.587879ms)
|
||||||
|
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (479.050454ms)
|
||||||
|
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (588.912231ms)
|
||||||
|
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (482.392087ms)
|
||||||
|
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5177.342358ms)
|
||||||
|
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (759.928005ms)
|
||||||
|
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (638.449802ms)
|
||||||
|
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (504.132634ms)
|
||||||
|
✔ K6: recover without proof, without confirmation, or with changed pins is refused (705.673779ms)
|
||||||
|
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (366.285834ms)
|
||||||
|
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (503.763243ms)
|
||||||
|
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3278.897436ms)
|
||||||
|
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (22.363818ms)
|
||||||
|
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (412.870075ms)
|
||||||
|
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (271.469127ms)
|
||||||
|
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (82.915676ms)
|
||||||
|
✔ R1: after a proven force stop the controller releases the scope; the shim and the unit are gone (387.797964ms)
|
||||||
|
✔ R2: close releases the scope of a binding proven stopped, once, when the stop's own release hasn't run (555.58013ms)
|
||||||
|
✔ R3: no release after an unavailable stop or an unverified proof: the scope and its shim stay as evidence (1091.627404ms)
|
||||||
|
✔ E1: a natural engine exit with no member left: an engine-exit stop, stopped on a proof naming the engine, and the scope released (375.282304ms)
|
||||||
|
✔ E2: an engine exit with a tool child alive: the stop ends uncertain and nothing is released; a force stop then ends it (2489.640757ms)
|
||||||
|
✔ E3: an engine exit and a force stop take one escalation slot, in either order (1076.608738ms)
|
||||||
|
✔ E4: a shim that doesn't answer: the engine-exit stop misses its deadline, ends uncertain and frees the slot (942.692322ms)
|
||||||
|
✔ E5: an engine exit after a force stop ended uncertain starts no engine-exit stop; the force stop stays current (2571.843221ms)
|
||||||
|
✔ E6: the engine-exit observation refuses a reaped process that isn't the recorded engine: another PID or start ticks (355.145164ms)
|
||||||
|
✔ 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) (373.428055ms)
|
||||||
|
✔ R6: releaseCohort releases nothing while the engine runs, for another invocation ID, or for a cohort without a scope (128.555417ms)
|
||||||
|
✔ R7: controller killed after the claim is stopped, before the release: the restart releases the scope (2628.217217ms)
|
||||||
|
✔ R8: controller killed between the two keys' stopped revisions: the restart releases the scope (400.258408ms)
|
||||||
|
✔ R9: a release that ended unavailable is tried again on a confirmed recover, not a refused one (484.350681ms)
|
||||||
|
✔ R10: close releases and signals nothing for a stopped binding whose proof no longer verifies (Filbert N3 on #1536) (387.771436ms)
|
||||||
|
✔ R11: close({ killEngine: true }) after a proven stop sends no signal to the recorded engine PID; a restart releases nothing more (2583.058032ms)
|
||||||
|
✔ R12: a restart onto a claim stopped on a boot proof releases nothing, though its scope is listed (247.132436ms)
|
||||||
|
✔ no shim from this file's tests is left running for the after hook (3.616362ms)
|
||||||
|
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (213.962952ms)
|
||||||
|
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (200.145515ms)
|
||||||
|
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (204.845565ms)
|
||||||
|
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (274.399305ms)
|
||||||
|
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (188.149792ms)
|
||||||
|
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.51338ms)
|
||||||
|
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (185.443ms)
|
||||||
|
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (302.307245ms)
|
||||||
|
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (205.503703ms)
|
||||||
|
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (194.659906ms)
|
||||||
|
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (202.907213ms)
|
||||||
|
✔ 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 (184.169445ms)
|
||||||
|
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.01457ms)
|
||||||
|
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (190.185216ms)
|
||||||
|
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (212.690905ms)
|
||||||
|
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (240.040013ms)
|
||||||
|
✔ terminal: engine control characters are made visible; a lost connection refuses submit (197.332008ms)
|
||||||
|
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.417627ms)
|
||||||
|
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.274431ms)
|
||||||
|
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.346698ms)
|
||||||
|
✔ 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.504414ms)
|
||||||
|
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (30.118555ms)
|
||||||
|
✔ 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.486281ms)
|
||||||
|
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.854307ms)
|
||||||
|
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.436365ms)
|
||||||
|
✔ 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.387433ms)
|
||||||
|
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.107034ms)
|
||||||
|
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (243.597552ms)
|
||||||
|
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (248.493387ms)
|
||||||
|
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (288.916396ms)
|
||||||
|
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (758.140593ms)
|
||||||
|
✔ H4: self-takeover is refused (192.619981ms)
|
||||||
|
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (447.129497ms)
|
||||||
|
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (981.519907ms)
|
||||||
|
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (215.896453ms)
|
||||||
|
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (558.886253ms)
|
||||||
|
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (253.203441ms)
|
||||||
|
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (131.776635ms)
|
||||||
|
✔ H13: a retry with the same request ID and different text is refused (191.39983ms)
|
||||||
|
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (591.111694ms)
|
||||||
|
✔ H15: a revoked connection's command is refused; the revocation fence holds (273.913871ms)
|
||||||
|
✔ H16: a second controller for the same session refuses already-active; the first is untouched (195.491354ms)
|
||||||
|
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (810.713077ms)
|
||||||
|
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (358.49547ms)
|
||||||
|
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (696.391961ms)
|
||||||
|
✔ H18: two prompts before any native output: the second refuses busy; one engine write (195.654876ms)
|
||||||
|
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (337.579383ms)
|
||||||
|
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.686313ms)
|
||||||
|
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (666.744066ms)
|
||||||
|
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (561.95497ms)
|
||||||
|
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (713.768911ms)
|
||||||
|
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2992.506224ms)
|
||||||
|
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (657.545175ms)
|
||||||
|
✔ no shim from this file's tests is left running for the after hook (4.693478ms)
|
||||||
|
✔ a plain conversation: catalogue row, one page, CHAT-01 records (5.816744ms)
|
||||||
|
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.497311ms)
|
||||||
|
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.487694ms)
|
||||||
|
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.186334ms)
|
||||||
|
✔ F1: an unreadable fork is never merged into another branch's history (1.276149ms)
|
||||||
|
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.673301ms)
|
||||||
|
✔ F1: a file whose entries are all unreadable shows a notice per line (0.678294ms)
|
||||||
|
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.137795ms)
|
||||||
|
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.46739ms)
|
||||||
|
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.781729ms)
|
||||||
|
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (7.719914ms)
|
||||||
|
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (8.068321ms)
|
||||||
|
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (8.965074ms)
|
||||||
|
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.832893ms)
|
||||||
|
✔ F8: a file swapped for a symlink after the catalogue is refused (1.83709ms)
|
||||||
|
✔ F9: registrations never add or redirect a root (1.745933ms)
|
||||||
|
✔ F10: a header cwd naming another project is refused (4.302579ms)
|
||||||
|
✔ F11: parentSession renders with a marker and the parent is never opened (0.804473ms)
|
||||||
|
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.399211ms)
|
||||||
|
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.289964ms)
|
||||||
|
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.515587ms)
|
||||||
|
✔ F13: compaction is a marker in place, then the retained content (0.823255ms)
|
||||||
|
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (539.349415ms)
|
||||||
|
✔ fragments never cut a surrogate pair and keep an empty string (4.950008ms)
|
||||||
|
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.52424ms)
|
||||||
|
✔ unknown conversations, empty files and non-Pi files refuse (2.38432ms)
|
||||||
|
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.089171ms)
|
||||||
|
✔ a seat directory without search permission refuses that root, not the catalogue (2.061333ms)
|
||||||
|
✔ every page and cursor is a valid CHAT-01 record (848.313836ms)
|
||||||
|
✔ the engine pin holds for the installed package (2.479422ms)
|
||||||
|
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (281.112065ms)
|
||||||
|
✔ 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) (545.934981ms)
|
||||||
|
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (294.337157ms)
|
||||||
|
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (490.153381ms)
|
||||||
|
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (418.729452ms)
|
||||||
|
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (229.15125ms)
|
||||||
|
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.118893ms)
|
||||||
|
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (337.525917ms)
|
||||||
|
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (205.099093ms)
|
||||||
|
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (299.824716ms)
|
||||||
|
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (434.359793ms)
|
||||||
|
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1873.752634ms)
|
||||||
|
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (192.83246ms)
|
||||||
|
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (283.974738ms)
|
||||||
|
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (142.097687ms)
|
||||||
|
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (228.773448ms)
|
||||||
|
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (385.847161ms)
|
||||||
|
✔ N10: fake conformance (33.63854ms)
|
||||||
|
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (414.009399ms)
|
||||||
|
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (267.726698ms)
|
||||||
|
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (292.450442ms)
|
||||||
|
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (340.713029ms)
|
||||||
|
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (314.13874ms)
|
||||||
|
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (353.203265ms)
|
||||||
|
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (167.218756ms)
|
||||||
|
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (333.026483ms)
|
||||||
|
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (651.586954ms)
|
||||||
|
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (575.783578ms)
|
||||||
|
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (489.649379ms)
|
||||||
|
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (217.509557ms)
|
||||||
|
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (165.236831ms)
|
||||||
|
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (194.665108ms)
|
||||||
|
✔ 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 (209.835323ms)
|
||||||
|
✔ 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 (170.106784ms)
|
||||||
|
ℹ tests 182
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 182
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 47558.010294
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (5.621138ms)
|
||||||
|
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.680647ms)
|
||||||
|
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.518448ms)
|
||||||
|
✔ approvals: a button approves only on its own request message with the matching custom id (0.454719ms)
|
||||||
|
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (16.936169ms)
|
||||||
|
✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (5.796088ms)
|
||||||
|
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.36279ms)
|
||||||
|
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.003271ms)
|
||||||
|
✔ authorize: open channel, listed user (1.652167ms)
|
||||||
|
✔ authorize: wrong guild (0.171208ms)
|
||||||
|
✔ authorize: no guild (DM) (0.160323ms)
|
||||||
|
✔ authorize: unlisted channel (0.198386ms)
|
||||||
|
✔ authorize: unknown channel, no info (0.167121ms)
|
||||||
|
✔ authorize: thread of listed parent (0.168744ms)
|
||||||
|
✔ authorize: thread of unlisted parent (0.146603ms)
|
||||||
|
✔ authorize: text channel that is not a thread and not listed (0.122799ms)
|
||||||
|
✔ authorize: unlisted user (0.178353ms)
|
||||||
|
✔ authorize: no author (0.24445ms)
|
||||||
|
✔ authorize: bot author (listed id, bot flag) (0.142254ms)
|
||||||
|
✔ authorize: system author (0.1204ms)
|
||||||
|
✔ authorize: the bot itself (0.085571ms)
|
||||||
|
✔ authorize: webhook (0.107056ms)
|
||||||
|
✔ authorize: mention channel without mention (0.136805ms)
|
||||||
|
✔ authorize: mention channel with bot mention (0.168034ms)
|
||||||
|
✔ authorize: mention channel with @everyone only (0.102798ms)
|
||||||
|
✔ authorize: mention channel mentioning someone else (0.078337ms)
|
||||||
|
✔ authorize: mention channel, content says @bot but mentions empty (0.104389ms)
|
||||||
|
✔ authorize: private thread under mention channel, mentioned (0.165399ms)
|
||||||
|
✔ authorize: private thread under mention channel, not mentioned (0.086301ms)
|
||||||
|
✔ authorize: thread in another guild per channel info (0.092613ms)
|
||||||
|
✔ authorize: not an object (0.066904ms)
|
||||||
|
✔ authorize: no id (0.077958ms)
|
||||||
|
✔ authorize: oversize content is accepted and flagged (0.074522ms)
|
||||||
|
✔ authorize: exactly the limit is not oversize (0.06417ms)
|
||||||
|
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (2.551883ms)
|
||||||
|
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.220013ms)
|
||||||
|
✔ binding: a complete binding validates and is frozen (2.357386ms)
|
||||||
|
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.31584ms)
|
||||||
|
✔ binding: empty allowlists refuse (0.359098ms)
|
||||||
|
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.47848ms)
|
||||||
|
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.824106ms)
|
||||||
|
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.429225ms)
|
||||||
|
✔ binding: file must be 0600, regular, not a symlink (1.707159ms)
|
||||||
|
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.447503ms)
|
||||||
|
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (83.731352ms)
|
||||||
|
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.68817ms)
|
||||||
|
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (275.0755ms)
|
||||||
|
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (178.538881ms)
|
||||||
|
✔ cli: run refuses when STOP is present, before any network use (113.791635ms)
|
||||||
|
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.53592ms)
|
||||||
|
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.045274ms)
|
||||||
|
✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (17.982527ms)
|
||||||
|
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (14.517131ms)
|
||||||
|
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.608977ms)
|
||||||
|
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.115925ms)
|
||||||
|
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.212085ms)
|
||||||
|
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.286426ms)
|
||||||
|
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.817618ms)
|
||||||
|
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.773592ms)
|
||||||
|
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (0.637674ms)
|
||||||
|
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (0.615422ms)
|
||||||
|
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.316455ms)
|
||||||
|
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.691238ms)
|
||||||
|
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (5.347178ms)
|
||||||
|
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (2.591234ms)
|
||||||
|
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.2259ms)
|
||||||
|
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (4.047464ms)
|
||||||
|
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.144911ms)
|
||||||
|
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.383147ms)
|
||||||
|
✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (1.798709ms)
|
||||||
|
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.692522ms)
|
||||||
|
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (2.298643ms)
|
||||||
|
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (1.805063ms)
|
||||||
|
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (3.515655ms)
|
||||||
|
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.271141ms)
|
||||||
|
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.750338ms)
|
||||||
|
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.788425ms)
|
||||||
|
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.168066ms)
|
||||||
|
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.257101ms)
|
||||||
|
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.164515ms)
|
||||||
|
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.613854ms)
|
||||||
|
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.210113ms)
|
||||||
|
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.398696ms)
|
||||||
|
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (49.941316ms)
|
||||||
|
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (35.945233ms)
|
||||||
|
✔ engine: one prompt, one turn, text and usage come back (29.460184ms)
|
||||||
|
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (342.103959ms)
|
||||||
|
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (234.744055ms)
|
||||||
|
✔ engine: timeout sends abort and fails only that turn; the process stays (114.07374ms)
|
||||||
|
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (225.855113ms)
|
||||||
|
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (124.745032ms)
|
||||||
|
✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (212.906977ms)
|
||||||
|
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (615.470545ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.274665ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.468708ms)
|
||||||
|
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (429.835823ms)
|
||||||
|
✔ engine: a malformed JSONL line fails the turn, not the process (23.565294ms)
|
||||||
|
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.822334ms)
|
||||||
|
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.224148ms)
|
||||||
|
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.68177ms)
|
||||||
|
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.472702ms)
|
||||||
|
✔ gateway: op 9 resumable resumes (0.361691ms)
|
||||||
|
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.7762ms)
|
||||||
|
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.747024ms)
|
||||||
|
✔ gateway: close() is final and unparseable frames are ignored (0.385924ms)
|
||||||
|
✔ git: config validation is strict, needs write: true, a work tree and a private token file (58.498456ms)
|
||||||
|
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (33.811139ms)
|
||||||
|
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (61.452056ms)
|
||||||
|
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.26859ms)
|
||||||
|
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (75.940799ms)
|
||||||
|
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (78.462776ms)
|
||||||
|
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (70.956886ms)
|
||||||
|
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (175.920526ms)
|
||||||
|
✔ git: push pushes the named branch only and reports up to date (57.726389ms)
|
||||||
|
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (77.296175ms)
|
||||||
|
✔ git: the credential helper answers get over https from a private file and nothing else (177.540896ms)
|
||||||
|
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (691.450933ms)
|
||||||
|
✔ lock: the claim is exclusive; a second start against a live owner refuses (4.521372ms)
|
||||||
|
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (62.950939ms)
|
||||||
|
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (0.728587ms)
|
||||||
|
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (2.344403ms)
|
||||||
|
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (32.835968ms)
|
||||||
|
✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (264.367061ms)
|
||||||
|
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.398982ms)
|
||||||
|
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (18.185283ms)
|
||||||
|
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.975232ms)
|
||||||
|
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (33.01571ms)
|
||||||
|
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (129.422656ms)
|
||||||
|
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (75.740495ms)
|
||||||
|
✔ notices: a kind is recorded per UTC day and found again (0.544549ms)
|
||||||
|
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (4.759033ms)
|
||||||
|
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.218671ms)
|
||||||
|
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.693603ms)
|
||||||
|
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (37.697099ms)
|
||||||
|
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (35.31509ms)
|
||||||
|
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (23.253456ms)
|
||||||
|
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (65.109038ms)
|
||||||
|
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (593.484655ms)
|
||||||
|
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.40986ms)
|
||||||
|
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.5306ms)
|
||||||
|
✔ rest: content and nonce limits are enforced locally; typing never throws (0.655324ms)
|
||||||
|
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.659433ms)
|
||||||
|
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.046431ms)
|
||||||
|
✔ setspark config: a bare https or loopback origin, a private key file, a principal (4.550764ms)
|
||||||
|
✔ setspark config: reaches the tools config and the binding as a fixed key (2.579111ms)
|
||||||
|
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.397493ms)
|
||||||
|
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (1.812113ms)
|
||||||
|
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (19.123663ms)
|
||||||
|
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (7.397458ms)
|
||||||
|
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (4.870247ms)
|
||||||
|
✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.343287ms)
|
||||||
|
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.584902ms)
|
||||||
|
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (2.58714ms)
|
||||||
|
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1007.144596ms)
|
||||||
|
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.434932ms)
|
||||||
|
✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (5.87492ms)
|
||||||
|
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.138595ms)
|
||||||
|
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.186415ms)
|
||||||
|
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.225947ms)
|
||||||
|
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.635986ms)
|
||||||
|
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.006748ms)
|
||||||
|
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.649056ms)
|
||||||
|
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.4467ms)
|
||||||
|
✔ tools: listing and search caps hold (8.834802ms)
|
||||||
|
✔ tools: credential shapes are caught; ordinary prose and ids are not (0.664563ms)
|
||||||
|
✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (4.509559ms)
|
||||||
|
✔ tools: an unreadable file under the root is skipped by search and refused by read (1.096556ms)
|
||||||
|
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (0.931244ms)
|
||||||
|
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.311411ms)
|
||||||
|
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.020865ms)
|
||||||
|
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.174818ms)
|
||||||
|
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.082654ms)
|
||||||
|
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.109541ms)
|
||||||
|
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1023.846649ms)
|
||||||
|
✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (4.134412ms)
|
||||||
|
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.209292ms)
|
||||||
|
✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (2.50961ms)
|
||||||
|
✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (2.156326ms)
|
||||||
|
ℹ tests 178
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 178
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2603.207095
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
✔ sessionModel: agent vars win, then the system's execution settings (13.631434ms)
|
||||||
|
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (5.334979ms)
|
||||||
|
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.925694ms)
|
||||||
|
✔ the prompt's built-in tools line is the harness's own names, as the manifest lists them (11.350753ms)
|
||||||
|
✔ the prompt names the project clones in the workspace, and none when there are none (7.825398ms)
|
||||||
|
✔ the prompt explains the review loop to a role with push_working, and reviews to one with fetch_review (6.899916ms)
|
||||||
|
✔ a bundle is written once: an existing file refuses (1.921315ms)
|
||||||
|
✔ a path with a single quote can't go into the hook command (2.33337ms)
|
||||||
|
✔ allow exits 0, a deny exits 2 with the reason on stderr (126.30648ms)
|
||||||
|
✔ a missing or wrong policy, or a bad event, exits 2 (84.122122ms)
|
||||||
|
✔ the bundle's wrapped command: a missing gate or node still blocks (1092.925085ms)
|
||||||
|
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (804.631426ms)
|
||||||
|
✔ claude: the hook alone blocks a path outside the workspace (471.284235ms)
|
||||||
|
✔ claude: a second turn resumes the first turn's session (794.447494ms)
|
||||||
|
✔ claude adapter: --restricted is always passed (5.276069ms)
|
||||||
|
✔ claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable (16.051166ms)
|
||||||
|
✔ claude adapter: a directory pin exits 2 before it runs; a pin with a space runs (5.495306ms)
|
||||||
|
✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (702.126561ms)
|
||||||
|
✔ claude: a missing hook or MCP file refuses before claude starts (6.875762ms)
|
||||||
|
✔ pi: policy tools and typed tools pass, anything else is blocked (3.151386ms)
|
||||||
|
✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.81153ms)
|
||||||
|
✔ file tool paths must resolve inside the workspace (1.494932ms)
|
||||||
|
✔ pi's own path normalisation can't be used to step out (1.351351ms)
|
||||||
|
✔ a symlink inside the workspace that points out is outside (1.248688ms)
|
||||||
|
✔ a dangling symlink is refused at any depth, in both harnesses (4.728526ms)
|
||||||
|
✔ read is checked under every spelling pi's read would open, in both harnesses (22.086122ms)
|
||||||
|
✔ other spellings cover directories, dangling links and pi's cwd (1.970961ms)
|
||||||
|
✔ a relative path climbs from the workspace's real path, in both harnesses (6.817326ms)
|
||||||
|
✔ claude path fields per tool (0.802524ms)
|
||||||
|
✔ glob patterns stay inside the workspace (0.749681ms)
|
||||||
|
✔ a path that can't be checked is blocked (0.482911ms)
|
||||||
|
✔ fetch_review's have lists the working refs before origin's, and no more than HAVE_MAX (226.273786ms)
|
||||||
|
✔ fetch_review's have orders the working refs newest first within each clone, one clone after another (176.793169ms)
|
||||||
|
✔ a runner git past its time: SIGTERM reaches its process group, and SIGKILL follows after the grace period (1810.26278ms)
|
||||||
|
✔ once a signalled runner git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (304.098196ms)
|
||||||
|
✔ initialize, ping and tools/list (57.646317ms)
|
||||||
|
✔ tools/call goes through the tool socket; a refusal is an isError result (32.593553ms)
|
||||||
|
✔ replies are compact JSON; over the cap a tool error names the size and the cap (36.220775ms)
|
||||||
|
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (33.349179ms)
|
||||||
|
✔ a missing argument is a usage error (28.165679ms)
|
||||||
|
✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (400.22475ms)
|
||||||
|
✔ pi: a reply over the cap is a tool error naming the size and the cap; list_tasks asks for open tasks (339.431181ms)
|
||||||
|
✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (333.30204ms)
|
||||||
|
✔ pi: a read is refused when pi would open another spelling outside (345.905292ms)
|
||||||
|
✔ pi: a relative path climbs from the real path of a workspace behind a symlink (333.920638ms)
|
||||||
|
✔ pi: a relative path climbs from the real path of a dataRoot behind a symlink (329.325719ms)
|
||||||
|
✔ pi: a missing extension refuses before any model call (5.495423ms)
|
||||||
|
✔ pi: an extension without its configuration fails pi's start (250.090632ms)
|
||||||
|
✔ founderCheck: founder variables, then a needed service without a usable token (1.725096ms)
|
||||||
|
✔ turnRequest names the sender, class, reply and decision (0.333692ms)
|
||||||
|
✔ turnRequest says a tester's message is a test and never Jason's instruction (0.425202ms)
|
||||||
|
✔ turnRequest shows the message's own sent time, a day old as well as new (0.213431ms)
|
||||||
|
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.323995ms)
|
||||||
|
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (224.831261ms)
|
||||||
|
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (242.009312ms)
|
||||||
|
✔ a task closed on the tracker is missing from the next turn's open list (217.669095ms)
|
||||||
|
✔ a message read a day after it was sent shows the time it was sent (178.086605ms)
|
||||||
|
✔ a decision the human resolves becomes a turn citing the decision and the answer (238.213198ms)
|
||||||
|
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (144.959388ms)
|
||||||
|
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (104.934163ms)
|
||||||
|
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (386.862169ms)
|
||||||
|
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1301.9623ms)
|
||||||
|
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (147.612273ms)
|
||||||
|
✔ founder credentials stop before the claim (20) (169.394611ms)
|
||||||
|
✔ a refused claim exits 21; an ended run's capability exits 22 (195.147258ms)
|
||||||
|
✔ the launch ending under a running session exits 22 (145.411067ms)
|
||||||
|
✔ a broker that stays unreachable exits 23 after brokerRetries polls (254.534966ms)
|
||||||
|
✔ a broker that is down at the claim exits 23, not 21 (100.963116ms)
|
||||||
|
✔ no capability, or a malformed one, on stdin exits 2 (201.219652ms)
|
||||||
|
✔ a missing or malformed policy exits 2 before the claim (131.618187ms)
|
||||||
|
✔ the PM gets launch, its task verbs and the reads (7.625345ms)
|
||||||
|
✔ list_requirements reads the requirement ids, for a task.create holder with a tracker (6.328199ms)
|
||||||
|
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.049492ms)
|
||||||
|
✔ a coder with a tracker gets its own task tools and no task_reassign (1.61953ms)
|
||||||
|
✔ launch only when the business's launch block names the instance as launcher (2.775558ms)
|
||||||
|
✔ an action outside the instance's authority has no tool (1.338832ms)
|
||||||
|
✔ the review loop: push_working and request_review for the coder, fetch_review and review_verdict for the reviewer, none for the PM (4.850979ms)
|
||||||
|
✔ callTool: one JSON line out, the result back, a refusal rejects (11.041601ms)
|
||||||
|
✔ list_tasks takes the broker's filters and pages; get_task takes a task_ref (3.411336ms)
|
||||||
|
✔ callTool sends list_tasks done false unless the session names done; other tools as given (3.569702ms)
|
||||||
|
✔ replyText: compact JSON up to the cap, an error naming the size and the cap over it (1.129709ms)
|
||||||
|
ℹ tests 81
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 81
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 11322.551241
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
✔ a raw token file, with or without one trailing newline, reaches curl only through the config stream (112.596435ms)
|
||||||
|
✔ the raw path accepts nothing else, and refuses before curl runs (343.252935ms)
|
||||||
|
✔ the file checks still apply on the raw path: mode, symlink, missing, directory (265.365245ms)
|
||||||
|
✔ the raw path base URL has no override (53.251437ms)
|
||||||
|
✔ the JSON path is unchanged, and JSON never falls through to the raw path (236.204291ms)
|
||||||
|
✔ a file that changes between the two reads refuses before curl runs, with or without a body (713.370775ms)
|
||||||
|
✔ the token reaches no child environment, even with an inherited CFG or SHELLOPTS=allexport (644.923626ms)
|
||||||
|
✔ real helper GET HTTP 200 preserves exit 0 without credentials (13.985662ms)
|
||||||
|
✔ real helper POST HTTP 201 preserves exit 0 without credentials (9.974403ms)
|
||||||
|
✔ real helper GET HTTP 403 preserves exit 1 without credentials (8.215584ms)
|
||||||
|
✔ fixture git subjects only, follow-ups and three session kinds (155.731988ms)
|
||||||
|
✔ text and JSON carry same numbers, open and truncated title (208.249206ms)
|
||||||
|
✔ missing credentials exit 2, no-issues never calls API and shows unknown (170.99742ms)
|
||||||
|
✔ empty range gives no rows and zero totals (145.58954ms)
|
||||||
|
✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (144.959642ms)
|
||||||
|
✔ close-only issue included, even median, missing metadata stays unknown (143.968858ms)
|
||||||
|
✔ unique commits but per-issue links count multiple tags once each (159.81512ms)
|
||||||
|
✔ issue pages of 50, 50 and 7 read 107 issues in 3 pages (316.464499ms)
|
||||||
|
✔ one short page reads 1 page; --no-issues reads none and says so (248.25564ms)
|
||||||
|
✔ a short page 10 is read; a full page 10 refuses at the ceiling (684.800819ms)
|
||||||
|
✔ an issue number on two pages refuses (149.094795ms)
|
||||||
|
✔ a failing page 2 refuses the whole read without its stderr (151.397967ms)
|
||||||
|
✔ page 2 with a malformed record refuses (155.022716ms)
|
||||||
|
✔ page 2 with more than 50 rows refuses (161.678724ms)
|
||||||
|
✔ bad API payload not JSON refuses (153.991244ms)
|
||||||
|
✔ bad API payload {} refuses (143.26862ms)
|
||||||
|
✔ bad API payload [{"number":1}] refuses (162.179964ms)
|
||||||
|
✔ partial or malformed session log refuses with location, not content (146.946281ms)
|
||||||
|
✔ a U+2028 or U+2029 inside a session string is one line, not a malformed record (138.667606ms)
|
||||||
|
✔ no sessions is an empty table; symlink source refuses (198.406022ms)
|
||||||
|
✔ reads only refactor even when another branch is checked out (166.926339ms)
|
||||||
|
✔ invalid dates, reverse dates and duplicate options refuse (116.464265ms)
|
||||||
|
✔ T3 agent assignments do not count as human in Table 2 (137.022158ms)
|
||||||
|
✔ preamble parsing and issue number boundaries (0.432345ms)
|
||||||
|
✔ T3 header: agent, or board from control-board; anything short of the full header is human (0.138894ms)
|
||||||
|
✔ no closed issues with human messages means undefined ratio, not invented zero (0.186017ms)
|
||||||
|
✔ T3: seat, archived, unmapped and Researcher threads count; imported, deleted and other-project threads do not (463.693586ms)
|
||||||
|
✔ T3: the default path is read from HOME and prints no path line; --no-t3 says so (672.883185ms)
|
||||||
|
✔ T3: a HOME with no database exits 1 and names --no-t3 (126.058453ms)
|
||||||
|
✔ T3: a file that is not a database exits 1 and names --no-t3 (140.861124ms)
|
||||||
|
✔ T3: a seat thread renamed to another seat exits 1 naming thread, title and roles (395.04346ms)
|
||||||
|
✔ T3: an unmapped thread addressed as a seat exits 1 (382.815261ms)
|
||||||
|
✔ T3: a header to another thread id is not cross-checked (397.981604ms)
|
||||||
|
✔ T3: no project, or two, for this root exits 1 (1039.538751ms)
|
||||||
|
✔ T3: a removed column exits 1 and names it (247.531731ms)
|
||||||
|
✔ T3: a missing table exits 1 and names it (242.276716ms)
|
||||||
|
✔ T3: a counted row with an unknown role exits 1 without its text (399.948767ms)
|
||||||
|
✔ T3: a counted row with role reasoning without the reasoning: id prefix exits 1 without its text (397.0135ms)
|
||||||
|
✔ T3: a counted row with non-text content exits 1 without its text (412.407281ms)
|
||||||
|
✔ T3: a counted row with an unparseable created_at exits 1 without its text (406.576103ms)
|
||||||
|
✔ T3: a reasoning summary is skipped before any check and counted as excluded (1061.389988ms)
|
||||||
|
✔ T3: a missing orchestration_events makes the diagnostic unknown and keeps the counts (699.28435ms)
|
||||||
|
✔ T3: a human message with no event counts in humanWithoutEvent (387.440149ms)
|
||||||
|
✔ T3: an unparseable event makes the diagnostic unknown and keeps the counts (703.252126ms)
|
||||||
|
✔ T3: an event with no string messageId makes the diagnostic unknown and keeps the counts (693.982101ms)
|
||||||
|
✔ T3: an error that is not from SQLite is rethrown, not reported as a database failure (349.874328ms)
|
||||||
|
✔ T3: a symlink at ~/.t3 exits 1 (133.549306ms)
|
||||||
|
✔ T3: a symlink at ~/.t3/userdata exits 1 (137.461189ms)
|
||||||
|
✔ T3: a symlink at ~/.t3/userdata/state.sqlite exits 1 (133.92017ms)
|
||||||
|
✔ T3: with --t3-db, a symlinked file or directory exits 1 (441.885872ms)
|
||||||
|
✔ T3 WAL: the newest message only in -wal, writer attached, is counted (387.496ms)
|
||||||
|
✔ T3 WAL: stopped cleanly, counts are correct and the main file is unchanged (429.478281ms)
|
||||||
|
✔ T3 WAL: -wal without -shm in a writable directory is read (480.761623ms)
|
||||||
|
✔ T3 WAL: -wal without -shm in a read-only directory exits 1 (406.343942ms)
|
||||||
|
✔ T3 WAL: stopped cleanly in a read-only directory exits 1 (398.546474ms)
|
||||||
|
✔ T3: a lock held past the 5 s busy timeout exits 1 and names --no-t3 (5394.748109ms)
|
||||||
|
✔ a done row whose closing issue is open is a violation; a row that is not done is not (1.752173ms)
|
||||||
|
✔ an issue several rows close is expected closed only once all of them are done (0.473996ms)
|
||||||
|
✔ closure needs positive evidence: unknown is undecided, and so is a skipped or short issue check (0.36392ms)
|
||||||
|
✔ each owner of an in-progress or in-review row gets one liveness class (7.337905ms)
|
||||||
|
✔ a required row not done after 14 days is a violation; a legacy row uses genesis as its lower bound (0.368144ms)
|
||||||
|
✔ an ISO requiredSince, as `set required` writes it, ages from its UTC day; one that does not parse is a violation (0.23185ms)
|
||||||
|
✔ the text section always ends in a count and a result, and never prints a full pass (0.303701ms)
|
||||||
|
✔ pidAlive: a running pid is present, an exited one is gone, and EPERM still means present (24.55336ms)
|
||||||
|
✔ issue states: open list first, then the metric page, then at most 10 lookups (261.826371ms)
|
||||||
|
✔ a full open list: lookups settle what it leaves out, and only an unsettled issue keeps it undecided (348.552563ms)
|
||||||
|
✔ the open list refuses on a failed call or a bad record, and never echoes the helper (153.454505ms)
|
||||||
|
✔ a helper call past the deadline is killed with its child, and the call reports it (2006.855521ms)
|
||||||
|
✔ readQueue loads queue.json through the queue validator and refuses anything else (124.008326ms)
|
||||||
|
✔ protected changes list every in-range entry that changes a required or parked row (417.671142ms)
|
||||||
|
✔ the CLI prints the queue section above the weekly table and under a queue key in --json (511.681912ms)
|
||||||
|
✔ a queue with nothing wrong prints 0 violations and a reduced pass, never a full pass (234.06589ms)
|
||||||
|
✔ --no-issues makes no call and leaves the issue checks undecided; --no-queue skips the section (288.041585ms)
|
||||||
|
✔ the CLI refuses a bad queue before any call, and a failed open list with exit 2 (286.160874ms)
|
||||||
|
✔ --unsupported-runtime repeats once per seat and takes a seat name (323.980299ms)
|
||||||
|
✔ an unreadable config makes every owner invalid instead of passing them (178.33289ms)
|
||||||
|
ℹ tests 86
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 86
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 22378.997266
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
✔ pure resolution selects current default or explicit enrolled account (1.87753ms)
|
||||||
|
✔ scope is explicit, bounded and never inferred (1.567153ms)
|
||||||
|
✔ fork pin is preserved against default change, override, missing account and revocation (0.709136ms)
|
||||||
|
✔ unenrolled account/provider, missing harness, model expansion and native model ceiling refuse (0.784548ms)
|
||||||
|
✔ only explicit synthetic credential forms and internal fixture stores admitted (8.644978ms)
|
||||||
|
✔ two concurrent workspaces of the same agent publish distinct complete private generations (50.699602ms)
|
||||||
|
✔ same execution ID is exclusively claimed and cannot overwrite a published generation (33.074452ms)
|
||||||
|
✔ failed generation after-auth preserves prior files, records failure and refuses blind same-ID retry (47.088974ms)
|
||||||
|
✔ failed generation before-publish preserves prior files, records failure and refuses blind same-ID retry (67.265607ms)
|
||||||
|
✔ credential lock contention refuses without duplicate side effects (11.142228ms)
|
||||||
|
✔ symlinked pre-existing final target is refused and never followed (25.892678ms)
|
||||||
|
✔ invalid registry cannot resolve; no fallback to supplied partial entries (0.284897ms)
|
||||||
|
✔ post-publication failure records uncertainty, preserves complete generation and prevents replay (26.445812ms)
|
||||||
|
✔ expired credentials refresh under transaction and subsequent generation reuses rotation (88.822519ms)
|
||||||
|
✔ refresh failure retains prior generation and store state (64.921613ms)
|
||||||
|
✔ refresh timeout retains prior generation and store state (143.382594ms)
|
||||||
|
✔ refresh malformed retains prior generation and store state (64.036033ms)
|
||||||
|
✔ concurrent refresh on same account refuses contention while unrelated account proceeds (222.63961ms)
|
||||||
|
✔ invalid refresh options refuse before burning claim (34.825711ms)
|
||||||
|
✔ fixed fake process rotates both OAuth fields without mutating caller input (25.928187ms)
|
||||||
|
✔ concurrent isolated processes preserve separate provider credentials (25.010968ms)
|
||||||
|
✔ fake failure is refused with fixed diagnostics (24.993962ms)
|
||||||
|
✔ fake malformed is refused with fixed diagnostics (22.259244ms)
|
||||||
|
✔ fake timeout is refused with fixed diagnostics (102.957235ms)
|
||||||
|
✔ fake unchanged is refused with fixed diagnostics (24.696725ms)
|
||||||
|
✔ caller executable/environment injection is rejected before spawning (0.295914ms)
|
||||||
|
✔ valid fixture tree validates and lists without secrets (64.894828ms)
|
||||||
|
✔ unknown-field refuses (0.357018ms)
|
||||||
|
✔ invalid-id refuses uppercase and traversal shapes (0.262255ms)
|
||||||
|
✔ plain-http baseUrl requires allowInsecureTransport (0.281252ms)
|
||||||
|
✔ native provider rejects allowInsecureTransport (0.127303ms)
|
||||||
|
✔ unsupported credential type and kind refuse (0.611856ms)
|
||||||
|
✔ account provider-path mismatch refuses (0.114494ms)
|
||||||
|
✔ profile account refs must be provider/account shaped (0.216201ms)
|
||||||
|
✔ seat selection accepts fork pin field, validates account refs (0.206759ms)
|
||||||
|
✔ harness manifest id must equal executable (gate 1) (0.193388ms)
|
||||||
|
✔ CLI validate: duplicate provider id across files refuses (27.807738ms)
|
||||||
|
✔ CLI validate: missing referenced provider/account refuse (31.715378ms)
|
||||||
|
✔ CLI validate: broken JSON refuses without secret echo (25.870752ms)
|
||||||
|
✔ CLI usage errors exit 2 (49.63936ms)
|
||||||
|
✔ credential.json sibling presence does not break validation and is never read (64.490388ms)
|
||||||
|
✔ D1 missing, empty and structurally empty roots refuse, no list projection (162.368012ms)
|
||||||
|
✔ D1 required directory auth cannot be absent (50.632938ms)
|
||||||
|
✔ D1 required directory auth/providers cannot be absent (49.33764ms)
|
||||||
|
✔ D1 required directory auth/accounts cannot be absent (61.31019ms)
|
||||||
|
✔ D1 required directory auth/settings cannot be absent (66.945094ms)
|
||||||
|
✔ D1 required directory harnesses cannot be absent (60.931445ms)
|
||||||
|
✔ D1 root file and unreadable metadata refuse (103.629135ms)
|
||||||
|
✔ D2 no symlink traversal at auth/providers/openai-codex.json (60.095567ms)
|
||||||
|
✔ D2 no symlink traversal at auth/accounts/openai-codex/homelab-openai (62.083693ms)
|
||||||
|
✔ D2 no symlink traversal at auth/providers (53.259606ms)
|
||||||
|
✔ D2 no symlink traversal at auth (53.216759ms)
|
||||||
|
✔ D2 root and ancestor symlinks and lexical traversal refuse (152.141119ms)
|
||||||
|
✔ private filesystem modes enforced for root (51.937594ms)
|
||||||
|
✔ private filesystem modes enforced for auth (50.444286ms)
|
||||||
|
✔ private filesystem modes enforced for auth/providers/openai-codex.json (55.086353ms)
|
||||||
|
✔ private filesystem modes enforced for auth/accounts/openai-codex/homelab-openai/account.json (62.426613ms)
|
||||||
|
✔ D3 numeric version 1 only across all record kinds (1.164071ms)
|
||||||
|
✔ D4 nested unknown keys and missing per-kind required fields refuse (59.136461ms)
|
||||||
|
✔ D5 unenrolled default refuses even when account exists (63.039432ms)
|
||||||
|
✔ D6 provider/account credential type must match (60.870421ms)
|
||||||
|
✔ D7 every harness endpoint enforces HTTP opt-in and shape (0.451383ms)
|
||||||
|
✔ D8 URLs reject embedded credentials and unsupported protocols without echo (166.083809ms)
|
||||||
|
✔ D9 malformed JSON diagnostics contain no content excerpt (59.913392ms)
|
||||||
|
✔ D10 missing metadata is missing-path, not invalid-json (59.378422ms)
|
||||||
|
✔ D10 library returns no partial entries on any invalid record (70.909709ms)
|
||||||
|
✔ null/scalar/array metadata refuses without stack or echo (240.736822ms)
|
||||||
|
✔ credential sibling is never opened, even when an unreadable symlink (34.6766ms)
|
||||||
|
✔ oversized metadata refuses before parsing (55.804946ms)
|
||||||
|
ℹ tests 69
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 69
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2079.298271
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
✔ F1: an ordinary commit after update-ref is refused until step 8; then it commits and the queue stays at C (1191.356783ms)
|
||||||
|
✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (991.385552ms)
|
||||||
|
ℹ git commit -e: index.lock free during the editor
|
||||||
|
✔ F1: a `commit -e -- path` whose guard ran before update-ref fails at its own HEAD update (977.647876ms)
|
||||||
|
ℹ git commit -e -- src.txt: index.lock held during the editor
|
||||||
|
✔ F1: step 8 with index.lock held exits 3, and ordinary commits stay refused until the printed command runs (1046.907592ms)
|
||||||
|
✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (1001.83685ms)
|
||||||
|
✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (1005.08238ms)
|
||||||
|
✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1491.946905ms)
|
||||||
|
✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (1127.562318ms)
|
||||||
|
✔ F1: a shared-index change during the procedure is not committed (1133.097061ms)
|
||||||
|
✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (1042.855242ms)
|
||||||
|
✔ F1: a missing or a different hook refuses (703.655534ms)
|
||||||
|
✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1156.731961ms)
|
||||||
|
✔ F1: the canary refuses a hook that git would not run (644.277355ms)
|
||||||
|
✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (922.471578ms)
|
||||||
|
✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (859.276223ms)
|
||||||
|
✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (858.603324ms)
|
||||||
|
✔ bootstrap: the archived tests and validator run outside any repository (887.113095ms)
|
||||||
|
✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (824.760468ms)
|
||||||
|
✔ general: a queue write after the snapshot is not committed (1218.736308ms)
|
||||||
|
✔ general: a snapshot whose log does not extend the base refuses (966.442659ms)
|
||||||
|
✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (152.549091ms)
|
||||||
|
✔ general: environment overrides, a linked worktree and usage (632.93992ms)
|
||||||
|
✔ general: a queue path staged before the run refuses at step 1 (649.155916ms)
|
||||||
|
✔ general: HEAD's queue tests failing in the archive refuse (847.059311ms)
|
||||||
|
✔ genesis document serializes deterministically and replays (4.549207ms)
|
||||||
|
✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (3.381448ms)
|
||||||
|
✔ a tampered result, receipt or viewSha fails replay (3.060361ms)
|
||||||
|
✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.16132ms)
|
||||||
|
✔ add: defaults for an ordinary seat, privileged extras, refusals (4.450922ms)
|
||||||
|
✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (4.733653ms)
|
||||||
|
✔ matrix: release, review round, changes requested and waiting-on-jason (15.45561ms)
|
||||||
|
✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (11.996962ms)
|
||||||
|
✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (17.670429ms)
|
||||||
|
✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (2.23442ms)
|
||||||
|
✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1279.048085ms)
|
||||||
|
✔ matrix: blocked keeps the claim and returns only to previousState (3.602083ms)
|
||||||
|
✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.376782ms)
|
||||||
|
✔ field edits: who may change what (4.87863ms)
|
||||||
|
✔ set issues keeps a logged narrowing of closes (N10) (2.465582ms)
|
||||||
|
✔ text the table shows refuses \ and <, everywhere it enters (N8) (1.705594ms)
|
||||||
|
✔ every accepted text renders to nine cells on every row (N8) (20.818558ms)
|
||||||
|
✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.491959ms)
|
||||||
|
✔ times and dates must be calendar values, not just the shape (2026-10-04) (1.494059ms)
|
||||||
|
✔ replay holds every op id to the caller's rule (N11) (4.553404ms)
|
||||||
|
✔ note: owner, listed reviewer or privileged; empty clears (1.057159ms)
|
||||||
|
✔ assign moves the claim with the owner; done clears it (2.298714ms)
|
||||||
|
✔ render is byte-stable and escapes pipes (0.705158ms)
|
||||||
|
✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.558857ms)
|
||||||
|
✔ next: resume, then review, then start, then wait, then nothing; lowest id first (16.267049ms)
|
||||||
|
✔ canonical args make a retry's identity independent of list order (0.281545ms)
|
||||||
|
✔ manifests, headings and blob ids (0.403442ms)
|
||||||
|
✔ the migration map: one queue-map block, exact keys (0.538414ms)
|
||||||
|
✔ every call but `queue` reaches the seat CLI exactly as before A2 (492.382164ms)
|
||||||
|
✔ no argument reaches the app entry in packages/cli, and only that call (71.626989ms)
|
||||||
|
✔ `queue` reaches the queue CLI with the rest of the arguments (170.509318ms)
|
||||||
|
✔ the pre-A2 fixture is the script A2 changed (0.27824ms)
|
||||||
|
✔ an old privileged add with no gate owner still replays through loadDoc, and gets jason (7.386899ms)
|
||||||
|
✔ a non-privileged add gets jason through the replay path and can't name a gate owner (1.877951ms)
|
||||||
|
✔ the CLI refuses a privileged add without --gate-owner, for jason and sage, and writes nothing (287.660913ms)
|
||||||
|
✔ the CLI add with --gate-owner carries the named owner and records a log entry (276.536235ms)
|
||||||
|
✔ the CLI add by a non-privileged seat gets jason and can't name a gate owner (305.015641ms)
|
||||||
|
✔ a retried sage add recorded with no gate owner answers (already recorded at rev N) and writes nothing (266.048687ms)
|
||||||
|
✔ acquire publishes the record by link; release removes only its own lock (9.576172ms)
|
||||||
|
✔ a kill between the temp write and the link leaves no lock (61.811999ms)
|
||||||
|
✔ a short or failed temp write refuses and leaves no lock and no temp (6.709365ms)
|
||||||
|
✔ a link error other than EEXIST refuses (9.167221ms)
|
||||||
|
✔ an error after the link releases the lock: unreadable gate, failing temp stat (24.402429ms)
|
||||||
|
✔ a release that fails on a gate path is reported, never a stack trace (P1) (36.324212ms)
|
||||||
|
✔ a paused holder: another writer waits 10 s, then refuses naming it live (10098.739804ms)
|
||||||
|
✔ two concurrent unlockers: the second refuses on the gate (37.849306ms)
|
||||||
|
✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (20.767567ms)
|
||||||
|
✔ a writer publishing during an unlock, gate first: the writer releases and refuses (27.526475ms)
|
||||||
|
✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (11.355623ms)
|
||||||
|
✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (19.70906ms)
|
||||||
|
✔ the same pid and start on a different boot is mismatch (0.408046ms)
|
||||||
|
✔ a foreign host is unknown whatever the local pid says; unlock refuses (57.976521ms)
|
||||||
|
✔ unreadable /proc: classification is unknown and acquire refuses (0.625267ms)
|
||||||
|
✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.185996ms)
|
||||||
|
✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (12.627477ms)
|
||||||
|
✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (30.410439ms)
|
||||||
|
✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (12.376322ms)
|
||||||
|
✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (46.915047ms)
|
||||||
|
✔ the migration map validates and renders the golden genesis table (3.988842ms)
|
||||||
|
✔ the marked QUEUE.md holds every row and parked item between its markers (1.385553ms)
|
||||||
|
✔ map-check reports each kind of drift (4.272718ms)
|
||||||
|
✔ a request posts once as the requester; a retry sends nothing (791.851996ms)
|
||||||
|
✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (4391.002716ms)
|
||||||
|
✔ the pre-send checks: GET user must name the requester, under the deadline (1485.397722ms)
|
||||||
|
✔ the lead's request refuses a token for login sage (1045.660332ms)
|
||||||
|
✔ the credential file: the seat's own, 0600, no symlink, never the shared default (924.168697ms)
|
||||||
|
✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (2310.104153ms)
|
||||||
|
✔ a same-op retry after a kill sends nothing, even with a stale view (2908.320364ms)
|
||||||
|
✔ a held lock at the outcome exits 3 and names what the transport said (706.601631ms)
|
||||||
|
✔ late outcomes: after an abandon, and after a resolve with the same or another id (2196.990885ms)
|
||||||
|
✔ resolve checks the comment: issue, markers, round, candidate and author (1572.635771ms)
|
||||||
|
✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (654.674821ms)
|
||||||
|
✔ validateRow checks a request round's shape, which every replayed entry must keep (482.88427ms)
|
||||||
|
✔ request, changes, a new candidate, approval: every round pinned; no review files (1724.581037ms)
|
||||||
|
✔ a row with no reviewers opens a round that posts nothing (1184.979584ms)
|
||||||
|
✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1362.136348ms)
|
||||||
|
✔ semantics: v1 entries replay as before; review entries need v2 (578.397341ms)
|
||||||
|
✔ set reviewers refuses the row's owner (2026-09-28) (418.768164ms)
|
||||||
|
✔ the owner records no verdict, even as a listed reviewer (610.448668ms)
|
||||||
|
✔ a request comment over the length limit is not sent (701.817497ms)
|
||||||
|
✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (837.540294ms)
|
||||||
|
✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (2917.269691ms)
|
||||||
|
✔ genesis: refusals before anything is written (404.54234ms)
|
||||||
|
✔ genesis: the map must be committed, well formed, with committed briefs and seats (595.896572ms)
|
||||||
|
✔ genesis: markers, a stray witness, once only; a retry returns the receipt (523.197469ms)
|
||||||
|
✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (381.90048ms)
|
||||||
|
✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (606.905448ms)
|
||||||
|
✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (826.116682ms)
|
||||||
|
✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (676.641417ms)
|
||||||
|
✔ a retried add returns the id it first allocated, after reassignment and after done (914.347707ms)
|
||||||
|
✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (673.137119ms)
|
||||||
|
✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (1203.542105ms)
|
||||||
|
✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (936.049753ms)
|
||||||
|
✔ add, set reviewers and assign refuse the row's owner as a reviewer (604.273965ms)
|
||||||
|
✔ the working-brief check: a changed working copy refuses the start and flags next (772.696138ms)
|
||||||
|
✔ next: resume first, then nothing for an idle seat; needs a seat (300.589202ms)
|
||||||
|
✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (646.119726ms)
|
||||||
|
✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1149.130993ms)
|
||||||
|
✔ a hand edit to queue.json refuses every verb, reads included (573.221351ms)
|
||||||
|
✔ verify and render --check leave bytes and mtimes unchanged (408.361525ms)
|
||||||
|
✔ render is byte-stable across runs and repositories (305.042353ms)
|
||||||
|
✔ snapshot and verify --snapshot (753.571506ms)
|
||||||
|
✔ usage errors exit 4 (546.48552ms)
|
||||||
|
✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (296.257635ms)
|
||||||
|
✔ a rename failure: nothing visible, temp removed (186.584088ms)
|
||||||
|
✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (241.286443ms)
|
||||||
|
✔ a directory fsync failure, then sync names the op (376.867428ms)
|
||||||
|
✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (234.286697ms)
|
||||||
|
✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (185.780843ms)
|
||||||
|
✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (208.086561ms)
|
||||||
|
✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (176.170354ms)
|
||||||
|
✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (211.445122ms)
|
||||||
|
✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (209.619403ms)
|
||||||
|
✔ unlock prints a swapped gate's warning on stderr, the result on stdout (151.853295ms)
|
||||||
|
✔ a view write that fails keeps the op and reports a stale view (181.26114ms)
|
||||||
|
✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (668.245373ms)
|
||||||
|
✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (695.016172ms)
|
||||||
|
✔ SIGKILL after the witness, before the view: the stale refusal names the op (650.588395ms)
|
||||||
|
✔ SIGKILL after the view, before the receipt: the retry returns the receipt (621.457423ms)
|
||||||
|
✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (243.596161ms)
|
||||||
|
✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (1018.098224ms)
|
||||||
|
✔ a deleted witness: refused after the locked recheck; accept-history records it absent (344.092264ms)
|
||||||
|
✔ a header edit during a write: the op stands, the view write is skipped with a warning (270.274026ms)
|
||||||
|
✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (264.101364ms)
|
||||||
|
✔ rows: every row as show prints it, in list order; writes nothing; same notes and refusals as list (279.015951ms)
|
||||||
|
✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (250.694854ms)
|
||||||
|
✔ a writer paused before and after the witness rename: readers see a tail, then a match (239.367482ms)
|
||||||
|
✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (650.715255ms)
|
||||||
|
✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (633.547015ms)
|
||||||
|
✔ the platform check refuses other filesystems (140.43136ms)
|
||||||
|
✔ tmpfs passes only a test layer that allows it (N5) (225.210569ms)
|
||||||
|
✔ unlock keeps a multi-line lock record on stdout (P3) (194.630648ms)
|
||||||
|
ℹ tests 156
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 156
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 29876.591312
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
✔ isRunId accepts the run id shape and nothing else (0.854358ms)
|
||||||
|
✔ a missing data root or runs directory lists nothing (1.345365ms)
|
||||||
|
✔ runs as a regular file lists nothing, as before (0.57279ms)
|
||||||
|
✔ listRunIds keeps r- names only, sorted oldest first (1.10759ms)
|
||||||
|
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (1.24486ms)
|
||||||
|
✔ readRunRecord returns documents and artifacts in directory order (0.839507ms)
|
||||||
|
✔ readRunRecord is null for a missing run, a dangling link or a file (0.581441ms)
|
||||||
|
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.596668ms)
|
||||||
|
✔ readRunDocument reads only the three run documents (0.432598ms)
|
||||||
|
✔ a link inside the data root is followed (0.807258ms)
|
||||||
|
✔ a data root that is itself a link is trusted as configured (0.553929ms)
|
||||||
|
✔ a run directory linked out of the data root is never read (0.561149ms)
|
||||||
|
✔ a document linked out of the data root reads as null (0.742461ms)
|
||||||
|
✔ a runs directory linked out of the data root refuses (0.515433ms)
|
||||||
|
✔ a relative link that climbs out of the data root refuses (0.356069ms)
|
||||||
|
✔ a sibling whose name starts with the data root's name is outside it (0.401066ms)
|
||||||
|
✔ a link loop refuses instead of reading as missing (0.528951ms)
|
||||||
|
✔ an unreadable runs directory refuses instead of listing nothing (0.362125ms)
|
||||||
|
✔ an unreadable run directory refuses instead of reading as missing (0.418414ms)
|
||||||
|
✔ a link to the data root's parent is outside it (0.320796ms)
|
||||||
|
✔ listRunIds sorts whatever order the directory returns (0.633197ms)
|
||||||
|
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.402101ms)
|
||||||
|
✔ the readers write nothing (1.616977ms)
|
||||||
|
✔ no pointer is null (1.511966ms)
|
||||||
|
✔ the pointer release.sh writes reads back (0.975616ms)
|
||||||
|
✔ a pointer that isn't the version 1 shape refuses with exit 2 (1.551705ms)
|
||||||
|
✔ a pointer that is a directory refuses with exit 4 (0.985125ms)
|
||||||
|
✔ a state file linked out of the data root refuses (0.772662ms)
|
||||||
|
✔ a state directory linked out of the data root refuses (0.482495ms)
|
||||||
|
✔ a state directory linked out to nothing reads as no release and an empty log (0.40081ms)
|
||||||
|
✔ a missing log is empty (0.307879ms)
|
||||||
|
✔ the log reads oldest first and counts malformed lines (0.831259ms)
|
||||||
|
✔ last must be a positive integer (0.398877ms)
|
||||||
|
✔ the state readers write nothing (0.684014ms)
|
||||||
|
✔ list prints each run in the established format (71.354595ms)
|
||||||
|
✔ list prints a damaged result.json as unknown and keeps listing the runs after it (65.27486ms)
|
||||||
|
✔ list prints each result field that isn't a string as unknown or - (67.592253ms)
|
||||||
|
✔ show prints the run in the established format (81.03627ms)
|
||||||
|
✔ show of a missing run and an invalid id exit 4 as before (105.701345ms)
|
||||||
|
✔ list and show refuse a runs directory linked out of the data root (122.620881ms)
|
||||||
|
✔ show refuses a run linked out of the data root; list reports it unknown (124.091068ms)
|
||||||
|
✔ list shows an r- name that isn't a valid run id, as before (60.476307ms)
|
||||||
|
✔ show refuses an invalid id before reading the config (34.083968ms)
|
||||||
|
ℹ tests 43
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 43
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 785.363241
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
✔ a first launch clones the root at its branch, with the root's path as origin (47.600261ms)
|
||||||
|
✔ the clone is not at its place until git clone has finished (24.123088ms)
|
||||||
|
✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (70.643084ms)
|
||||||
|
✔ a clone ahead of the root keeps its commits and counts as current (46.911498ms)
|
||||||
|
▶ a dirty clone, one on another branch and one diverged are kept as they are
|
||||||
|
✔ a modified file (40.771912ms)
|
||||||
|
✔ an untracked file (32.206935ms)
|
||||||
|
✔ a staged file (32.441762ms)
|
||||||
|
✔ another branch (33.874993ms)
|
||||||
|
✔ a detached HEAD (33.949006ms)
|
||||||
|
✔ a diverged branch (52.48051ms)
|
||||||
|
✔ a dirty clone, one on another branch and one diverged are kept as they are (226.958576ms)
|
||||||
|
▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it
|
||||||
|
✔ user.name (29.819171ms)
|
||||||
|
✔ user.email (33.665778ms)
|
||||||
|
✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (63.912975ms)
|
||||||
|
✔ a commit git can't read after the fast-forward is null (61.529094ms)
|
||||||
|
✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (18.661624ms)
|
||||||
|
▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it
|
||||||
|
✔ core.fsmonitor (36.460399ms)
|
||||||
|
✔ core.hooksPath (38.039859ms)
|
||||||
|
✔ a hook file (34.215452ms)
|
||||||
|
✔ include.path (33.05431ms)
|
||||||
|
✔ includeIf (33.66785ms)
|
||||||
|
✔ a filter driver (36.306921ms)
|
||||||
|
✔ origin moved (36.467348ms)
|
||||||
|
✔ a second origin url (32.411713ms)
|
||||||
|
✔ the origin url twice, both the root (35.104393ms)
|
||||||
|
✔ an ext:: origin (33.668484ms)
|
||||||
|
✔ a branch tracking another remote (31.270662ms)
|
||||||
|
✔ a branch merging a tag (34.981315ms)
|
||||||
|
✔ a fetch line onto local branches (34.377505ms)
|
||||||
|
✔ no origin (35.191585ms)
|
||||||
|
✔ a config file over the size cap (32.565847ms)
|
||||||
|
✔ config a symlink to /dev/zero (33.749335ms)
|
||||||
|
✔ the clone a symlink to the bait (33.098958ms)
|
||||||
|
✔ the clone a file (31.468179ms)
|
||||||
|
✔ the clone a plain directory (29.098967ms)
|
||||||
|
✔ .git a gitdir: file (34.856339ms)
|
||||||
|
✔ .git a symlink (37.330334ms)
|
||||||
|
✔ .git/logs a symlink (34.23585ms)
|
||||||
|
✔ .git/refs/heads a symlink (30.041626ms)
|
||||||
|
✔ .git/objects a symlink (29.915846ms)
|
||||||
|
✔ a reflog hard-linked to a file outside (34.507985ms)
|
||||||
|
✔ HEAD a fifo (32.161688ms)
|
||||||
|
✔ a commondir file (29.611541ms)
|
||||||
|
✔ an alternates file (31.837718ms)
|
||||||
|
✔ a gitlink staged, no .gitmodules (45.075123ms)
|
||||||
|
✔ a gitlink and .gitmodules staged (44.787143ms)
|
||||||
|
✔ a gitlink to a repository with its own gitlink (50.134514ms)
|
||||||
|
✔ a gitlink and .gitmodules committed (45.897371ms)
|
||||||
|
✔ a gitlink and submodule.active in config (43.090229ms)
|
||||||
|
✔ a .git/modules with no gitlink (32.741378ms)
|
||||||
|
✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1205.825811ms)
|
||||||
|
▶ a root that can't be cloned refuses with the project id and leaves nothing behind
|
||||||
|
✔ missing (10.218218ms)
|
||||||
|
✔ not a repository (9.910956ms)
|
||||||
|
✔ a plain directory inside another repository (19.675195ms)
|
||||||
|
✔ unreadable (10.896439ms)
|
||||||
|
✔ on no branch (12.113737ms)
|
||||||
|
✔ missing an object (19.480581ms)
|
||||||
|
✔ a root that can't be cloned refuses with the project id and leaves nothing behind (83.212147ms)
|
||||||
|
✔ a clone whose root is on no branch, or gone, is kept and named (36.370487ms)
|
||||||
|
✔ no credential or forge remote of the root reaches a clone (56.982313ms)
|
||||||
|
✔ every project in the business file gets its own clone at its own branch (34.164879ms)
|
||||||
|
✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (516.288499ms)
|
||||||
|
✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1016.219134ms)
|
||||||
|
✔ a timeout signals git's process group, so a process git started dies with it (513.761411ms)
|
||||||
|
✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (514.228086ms)
|
||||||
|
✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (1.903029ms)
|
||||||
|
✔ sandboxProblem names a missing bwrap and a bwrap that fails (9.924304ms)
|
||||||
|
✔ every bind says why; the bundle and session file stay read-only over the run directory (2.378585ms)
|
||||||
|
✔ the host's git socket is bound only when given, read-only and before the writable binds (1.130501ms)
|
||||||
|
✔ the git identity is the instance's, at <instance>@<business>.mosaic.invalid, and sandboxArgv refuses without it (1.888459ms)
|
||||||
|
✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (1947.964445ms)
|
||||||
|
✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2515.420417ms)
|
||||||
|
✔ a confined session that runs tester-cli.mjs is refused as tester, and the same shim outside the sandbox binds (272.703187ms)
|
||||||
|
✔ resolveSeat: by name under --repo resolves the repo layout (2.289193ms)
|
||||||
|
✔ resolveSeat: by path resolves the fleet layout (0.574638ms)
|
||||||
|
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (1.205164ms)
|
||||||
|
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.842128ms)
|
||||||
|
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.948143ms)
|
||||||
|
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.627706ms)
|
||||||
|
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.409671ms)
|
||||||
|
✔ CLI launch: registers, execs the fake launch script, and passes args through (38.819121ms)
|
||||||
|
✔ CLI launch: --harness lands in the record (28.835395ms)
|
||||||
|
✔ CLI launch: the launch script's own exit code passes through (28.757872ms)
|
||||||
|
✔ CLI launch: relaunching a seat rewrites the one registration record (62.895159ms)
|
||||||
|
✔ CLI launch: omitting --task records an empty string, not null (31.891483ms)
|
||||||
|
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (81.030298ms)
|
||||||
|
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (130.409604ms)
|
||||||
|
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.451796ms)
|
||||||
|
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.565729ms)
|
||||||
|
✔ 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.702232ms)
|
||||||
|
✔ 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.520942ms)
|
||||||
|
✔ 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 (260.842313ms)
|
||||||
|
✔ family: exactly one launch.max key in the model name, else null (0.826147ms)
|
||||||
|
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.859138ms)
|
||||||
|
✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.298556ms)
|
||||||
|
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.841099ms)
|
||||||
|
✔ session file and launch log: 0600, the session file written once (1.254723ms)
|
||||||
|
✔ endReason maps the runner's exit codes; a signal is killed (0.160932ms)
|
||||||
|
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (7.270227ms)
|
||||||
|
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (1.194764ms)
|
||||||
|
✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (333.270929ms)
|
||||||
|
✔ workRef and validBranch take only what a ref name can hold (1.363086ms)
|
||||||
|
✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (171.232977ms)
|
||||||
|
✔ a rewritten branch refuses non-fast-forward and the work ref stays (147.552298ms)
|
||||||
|
✔ a branch with no history in common with main refuses unrelated-history (71.965501ms)
|
||||||
|
✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (41.573634ms)
|
||||||
|
✔ main is fetched from the root again before each push, so a clone of a newer root pushes (152.12023ms)
|
||||||
|
✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (102.140744ms)
|
||||||
|
✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (77.322751ms)
|
||||||
|
✔ only a v2 bundle with the named branch is read (48.782288ms)
|
||||||
|
✔ a branch named main, or named for another task, moves only this task's work ref (99.741375ms)
|
||||||
|
✔ a record that throws moves the work ref back, or removes a new one (161.050944ms)
|
||||||
|
✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (359.422054ms)
|
||||||
|
✔ a root that can't be read refuses root-unreadable (27.238928ms)
|
||||||
|
✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2175.948221ms)
|
||||||
|
✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1029.576563ms)
|
||||||
|
✔ a working repository path that isn't a directory refuses (40.685679ms)
|
||||||
|
✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (112.977965ms)
|
||||||
|
✔ review of a project with no working repository refuses unknown-commit (12.711428ms)
|
||||||
|
✔ the runner's git tools hold the host's limits and branch rule (1.164502ms)
|
||||||
|
ℹ tests 120
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 120
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 4896.209811
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
✔ the boot config is checked before anything starts (158.899652ms)
|
||||||
|
✔ a business with no tracker entry refuses task verbs (132.655076ms)
|
||||||
|
✔ credential.expiring and .expired are recorded once per instance (212.155848ms)
|
||||||
|
✔ a token file that changes on disk records credential.changed (117.543303ms)
|
||||||
|
✔ autostart polls, reconciles and retries a startup the tracker was down for (139.61864ms)
|
||||||
|
✔ a refusal a restart must clear is not retried by the poll (109.738938ms)
|
||||||
|
✔ a poll that fires while two are queued is dropped (107.375788ms)
|
||||||
|
✔ close waits for a running verb and refuses one that has not started (228.267599ms)
|
||||||
|
✔ the bundled Vikunja is the pinned upstream image the runbook names (1.061585ms)
|
||||||
|
✔ every published port is on 127.0.0.1, and no secret is in the file (0.385514ms)
|
||||||
|
✔ the committed table loads frozen; its verbs are the broker task verbs (1.496309ms)
|
||||||
|
✔ a write names a verb the table has as its own key, not one it inherits (0.524871ms)
|
||||||
|
✔ the schema refuses any other shape (2.974849ms)
|
||||||
|
✔ the Field table in the slice 1 plan names the verbs each field is written by (0.682108ms)
|
||||||
|
✔ each verb refuses, asks for a decision and writes as the table and the role files say (1317.33804ms)
|
||||||
|
✔ the list holds the PRD's requirement ids, in its order, at its version (5.522234ms)
|
||||||
|
✔ the committed list loads frozen (0.732563ms)
|
||||||
|
✔ the schema refuses any other shape (3.520984ms)
|
||||||
|
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (424.735752ms)
|
||||||
|
✔ the project list holds recorded project objects in the recorded list envelope (117.274646ms)
|
||||||
|
✔ the recorded task bodies pass the checks S3 applies to every read (0.487689ms)
|
||||||
|
✔ the client works against the fake over real HTTP with the platform fetch (266.280272ms)
|
||||||
|
✔ a correct install starts, and the first reconcile records tasks that already exist (145.178245ms)
|
||||||
|
✔ verbs refuse while a business is starting and after startup refused it (137.522574ms)
|
||||||
|
✔ startup refuses a token that can do more than its role needs (444.632084ms)
|
||||||
|
✔ startup refuses an unsupported version and flags an untested one (315.47717ms)
|
||||||
|
✔ startup refuses a board that the runbook did not install (396.067579ms)
|
||||||
|
✔ startup refuses a project the sync bot cannot read (85.36499ms)
|
||||||
|
✔ startup refuses a configured label the pm bot cannot see (99.096578ms)
|
||||||
|
✔ startup refuses an expired credential and a missing sync credential (188.129749ms)
|
||||||
|
✔ an unreachable tracker refuses with tracker-unavailable (93.162049ms)
|
||||||
|
✔ an edit in the UI is recorded once, with the fields that changed (255.230586ms)
|
||||||
|
✔ a move between open buckets is seen on the board, though updated does not change (183.700355ms)
|
||||||
|
✔ a person's comment is counted and a bot's is not (303.025803ms)
|
||||||
|
✔ the hourly reconcile catches a comment through comment_count (240.784654ms)
|
||||||
|
✔ a task closed in the UI leaves the open view with its done bucket (419.16324ms)
|
||||||
|
✔ a task that leaves the board is recorded as deleted, moved or out of reach (290.375012ms)
|
||||||
|
✔ a poll that read before a verb wrote does not overwrite the verb (174.127466ms)
|
||||||
|
✔ a tracker fault during a tick is reported and the next tick catches up (177.308005ms)
|
||||||
|
✔ a malformed answer refuses the tick with tracker-shape (117.564678ms)
|
||||||
|
✔ no token value reaches the database, the log or a refusal (278.765552ms)
|
||||||
|
✔ the first look at a task counts only comments inside the window (166.85089ms)
|
||||||
|
✔ with descendants off the adapter reads no project list and no child, as in v0 (163.201955ms)
|
||||||
|
✔ a three-level tree is read per project, and a project outside it is not (115.716274ms)
|
||||||
|
✔ a watch-only child keeps the business ready, and a failed child read refuses as transient (191.853032ms)
|
||||||
|
✔ a root without the installed buckets refuses with descendants on (108.391681ms)
|
||||||
|
✔ the project list refuses without read_all, and skips archived projects and saved filters (232.002048ms)
|
||||||
|
✔ descendants must be true or false (103.526745ms)
|
||||||
|
✔ a move inside the tree is a moved tombstone plus a snapshot under the new ref (166.043937ms)
|
||||||
|
✔ a child made after startup joins the tree at the next reconcile (160.977209ms)
|
||||||
|
✔ a child that leaves the tree has its tasks recorded missing, and the ledger keeps their history (175.669005ms)
|
||||||
|
✔ a child deleted or hidden between reconciles drops out at the next tick, and the root still refuses (200.318517ms)
|
||||||
|
✔ a reconcile that fails keeps the previous tree (127.522564ms)
|
||||||
|
✔ a closed task the cursor missed takes its own project's done bucket (149.598654ms)
|
||||||
|
✔ closed tasks read at startup take their own project's done bucket (96.577791ms)
|
||||||
|
✔ a closed task the cursor returns takes its own project's done bucket (115.660226ms)
|
||||||
|
✔ a task moved out of a child that leaves the tree at the same time is moved, not no-access (121.599729ms)
|
||||||
|
✔ a task in a bucket outside the five keeps its raw bucket id (113.127023ms)
|
||||||
|
✔ bucket 0 on a watch-only task never becomes a bucket or a state (229.369952ms)
|
||||||
|
✔ a child whose kanban view is deleted still refuses the tick, and the reconcile reads it watch-only (156.397545ms)
|
||||||
|
✔ a child dropped at a tick comes back at the next reconcile once the bot reads it again (136.463117ms)
|
||||||
|
✔ task.create needs a recorded human request and a requirement id (246.046098ms)
|
||||||
|
✔ task.create takes only an approved requirement id (218.672726ms)
|
||||||
|
✔ task.create cites the request a received message carries, not the message's id (188.392166ms)
|
||||||
|
✔ only labels named in the business file can be written (258.765434ms)
|
||||||
|
✔ task.schedule sets and clears a due date and relations (295.886265ms)
|
||||||
|
✔ assign and reassign move the role bots and record task.assigned (324.95242ms)
|
||||||
|
✔ task.update.assigned is for the assignee and records task.state (316.939064ms)
|
||||||
|
✔ a wrong expected digest records task.conflict and writes nothing (189.767663ms)
|
||||||
|
✔ a cross-role verb needs a resolved decision, used once (219.065264ms)
|
||||||
|
✔ task.close needs a verdict; after it every verb refuses with task-done (248.750337ms)
|
||||||
|
✔ a task with pushed work closes only citing an approve verdict for its latest push, and a refusal writes nothing (293.940721ms)
|
||||||
|
✔ a lost answer is settled by a re-read and never retried (169.627458ms)
|
||||||
|
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (116.886934ms)
|
||||||
|
✔ a task the sync bot cannot read refuses and records nothing (89.856294ms)
|
||||||
|
✔ verbs and polls for one business run one at a time (161.93175ms)
|
||||||
|
✔ a due date with milliseconds is written to the second (163.978235ms)
|
||||||
|
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (132.817461ms)
|
||||||
|
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (85.090023ms)
|
||||||
|
✔ a create whose final read fails succeeds and records task.created (88.20382ms)
|
||||||
|
✔ an edit between the last write and the final read shows as external on the next poll (110.722362ms)
|
||||||
|
✔ task.created is recorded when a later label write fails (76.008097ms)
|
||||||
|
✔ every verb acts in an installed child, on the child's own board (309.83811ms)
|
||||||
|
✔ task.create refuses a project outside the tree, a watch-only one and one that is not an id (162.471878ms)
|
||||||
|
✔ with descendants off, task.create takes only the root project (75.086629ms)
|
||||||
|
✔ a verb on a watch-only task, or a relation to one, refuses with project-not-installed (99.119197ms)
|
||||||
|
✔ a related task is read before authority: in another project than its ref names, task-moved; missing, task-not-found (139.005438ms)
|
||||||
|
✔ a task moved to another project of the tree refuses its old ref with task-moved and takes its new one (96.394136ms)
|
||||||
|
✔ a verb and a create in the child record the final read of the child's board (86.473989ms)
|
||||||
|
✔ a create in the child whose final read fails records the child's todo bucket (95.165017ms)
|
||||||
|
✔ a lost answer in the child is settled by a re-read of the child's board (126.887846ms)
|
||||||
|
✔ every related task is read before authority, not only the first (100.137828ms)
|
||||||
|
ℹ tests 92
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 92
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 5381.346732
|
||||||
@@ -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 (6620.738017ms)
|
||||||
|
✔ 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 (7795.106491ms)
|
||||||
|
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5045.573935ms)
|
||||||
|
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
|
||||||
|
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5218.106436ms)
|
||||||
|
✔ the README says a refused field table names the file or the key at fault, never a path (3.328608ms)
|
||||||
|
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7439.462235ms)
|
||||||
|
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (374.915531ms)
|
||||||
|
✔ decisions route serves the roles and recent lists from the reader view (588.839098ms)
|
||||||
|
✔ credentials route serves the broker credential status from the reader view (319.785787ms)
|
||||||
|
✔ replies route serves the replies to one message from the reader view (1571.488908ms)
|
||||||
|
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (64.521935ms)
|
||||||
|
✔ bus refusals map to statuses and never carry a path or a stack (25.883175ms)
|
||||||
|
✔ humanCall speaks the human transport protocol without blocking the server (1721.43184ms)
|
||||||
|
✔ busReader takes --business, else the live bus host, else refuses (278.8479ms)
|
||||||
|
✔ humanCall kills a transport that runs past its timeout (559.907062ms)
|
||||||
|
✔ serve refuses a --business value that is not a business id (256.48924ms)
|
||||||
|
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4289.633563ms)
|
||||||
|
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3381.149552ms)
|
||||||
|
✔ conversation view: a newer session with no readable history keeps the marker (2985.8227ms)
|
||||||
|
✔ conversation view: seats without history say so and offer no reply (2298.45105ms)
|
||||||
|
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4816.788773ms)
|
||||||
|
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54132.559529ms)
|
||||||
|
✔ messages HTTP read pages and preserves metadata; bad arguments refuse before a read; other reads unchanged (388.872455ms)
|
||||||
|
✔ 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 (10351.136964ms)
|
||||||
|
✔ Messages nav count polls on Tasks and Board and clears on a Board refusal; messages 403 refuses local pages (3303.183242ms)
|
||||||
|
✔ copy writes the Console paths at the full id, read-only, and leaves an existing copy alone (2544.011423ms)
|
||||||
|
✔ copy refuses a commit off refactor, an unknown one, a worktree and a bad copies directory (5816.473696ms)
|
||||||
|
✔ copy writes nothing when an archived path is missing at the commit (1446.843357ms)
|
||||||
|
✔ a copy that loses the rename race leaves no read-only temp directory behind (1169.713817ms)
|
||||||
|
✔ a copy whose rename fails with no target refuses, writes no copy and leaves no temp directory (2452.180935ms)
|
||||||
|
✔ a copy that meets a target holding files says exists: and moves nothing into it (898.89558ms)
|
||||||
|
✔ a copy that meets an empty target directory replaces it by rename, not by moving into it (344.137875ms)
|
||||||
|
✔ serve runs from a copy: /healthz 200, the config check, roles and RELEASE from the copy, the queue from --checkout (654.261959ms)
|
||||||
|
✔ serve refuses a --checkout that is not an absolute directory path (218.349978ms)
|
||||||
|
✔ consoleReads runs the queue read under queueRoot and reads RELEASE under root (48.00092ms)
|
||||||
|
✔ preview options are all three or none, and each is checked (10.498035ms)
|
||||||
|
✔ the CLI refuses a partial or invalid preview flag set at start (2135.063225ms)
|
||||||
|
✔ the CLI with all three flags serves the preview mode (216.631704ms)
|
||||||
|
✔ a request for the public name needs the header equal to the user id, else 401 and nothing read (14.646986ms)
|
||||||
|
✔ the header name matches without regard to case, and the value compare is exact (2.914869ms)
|
||||||
|
✔ the public name with the header reads the bus views, and the Host and Origin are checked first (47.566522ms)
|
||||||
|
✔ in preview mode every write answers 403 read-only, from the public name and loopback, and reaches nothing (20.307362ms)
|
||||||
|
✔ loopback requests are unchanged in preview mode, and the bus views work with the board down (14.466063ms)
|
||||||
|
✔ a proxy that rewrites Host to the loopback port gets 403 on its first request, whatever the user (38.603577ms)
|
||||||
|
✔ a repeated Host is 400 before any other check, in preview mode and without it (12.647154ms)
|
||||||
|
✔ without the preview flags the public name is refused and writes still reach the board (8.168855ms)
|
||||||
|
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (4.470676ms)
|
||||||
|
✔ credential state from the date alone, as the bus states it (0.279502ms)
|
||||||
|
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1870.939055ms)
|
||||||
|
✔ queue: a refused read fails closed with the store's text, redacted (1780.279029ms)
|
||||||
|
✔ queue: a store that fails to load, or a child that dies, gives a fixed message with no path or stack (4182.679856ms)
|
||||||
|
✔ business: names, vars on the allowlist, authority, credential metadata only (32.681989ms)
|
||||||
|
✔ business: missing or invalid file refuses with the module's text, redacted (32.006458ms)
|
||||||
|
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (18.909671ms)
|
||||||
|
✔ settings: release, board, loopback address, notifier binding name only (1131.312007ms)
|
||||||
|
✔ task fields: the committed table with no reads configured, GET only; a bad table refuses with no path (45.949967ms)
|
||||||
|
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4285.945165ms)
|
||||||
|
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22953.88903ms)
|
||||||
|
✔ 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 (7344.106379ms)
|
||||||
|
✔ a sent receipt lists its replies as the page refreshes, oldest first and capped, and marks none delivered or read (13554.538088ms)
|
||||||
|
✔ startServer takes send only as a function and only with the preview mode (7.187778ms)
|
||||||
|
✔ the CLI refuses --send without the preview flags, and without a configured bus (478.474527ms)
|
||||||
|
✔ the CLI with --send and a configured bus sends through the human transport and logs one line (142.90697ms)
|
||||||
|
✔ without --send the route is read-only like every other write, and config says no send (8.86297ms)
|
||||||
|
✔ with --send every other write is still 403 read-only, and nothing reaches the board or bus (5.706096ms)
|
||||||
|
✔ config says send only on the public name with --send on (2.764273ms)
|
||||||
|
✔ a send without the signed-in user is 401 and is neither sent nor logged, loopback included (3.916192ms)
|
||||||
|
✔ each request check refuses before anything is sent, and logs the refusal without the body (11.825522ms)
|
||||||
|
✔ a send passes exactly to, body and in_reply_to, answers the id, and logs it (2.518326ms)
|
||||||
|
✔ a crafted role name cannot forge a log line (2.020652ms)
|
||||||
|
✔ a bus refusal is not sent, with its status; an outcome the bus could not confirm is unknown and never retried (6.418259ms)
|
||||||
|
✔ through the human transport the request is exactly the business, message.send and the keys (82.996357ms)
|
||||||
|
✔ against a real broker: the message is written once as a human send, and its refusals map (1291.224958ms)
|
||||||
|
✔ loopback host and board origin fail closed (5.822995ms)
|
||||||
|
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (240.310128ms)
|
||||||
|
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (47.41791ms)
|
||||||
|
✔ unreachable board reports URL; CLI rejects unsupported options (458.402965ms)
|
||||||
|
✔ render prints the unit without the preview flags (9.85434ms)
|
||||||
|
✔ render carries every serve flag, the preview set included (10.032225ms)
|
||||||
|
✔ a partial preview set is refused (18.588113ms)
|
||||||
|
✔ a value the unit cannot carry is refused, for every flag (362.683272ms)
|
||||||
|
✔ the rendered unit names none of the human proof agent markers and sets no agent variable (53.180084ms)
|
||||||
|
✔ install into --dir with --no-reload writes the file whole, then reports unchanged (114.704393ms)
|
||||||
|
✔ install without --no-reload runs daemon-reload and nothing that starts the unit (135.225336ms)
|
||||||
|
✔ uninstall refuses while the unit is running, with or without --no-reload, and removes it once it is not (337.882136ms)
|
||||||
|
✔ status prints the unit state and GET /healthz on the installed port (335.001609ms)
|
||||||
|
✔ usage: an unknown verb or flag, and start or enable, exit 4 (85.994715ms)
|
||||||
|
✔ without --send, render is the template with its placeholders filled, byte for byte (36.717901ms)
|
||||||
|
✔ render --send with the preview set ends ExecStart with --send and changes nothing else (56.551935ms)
|
||||||
|
✔ --send without the whole preview set is refused and writes nothing (170.565151ms)
|
||||||
|
✔ install --send into --dir with --no-reload writes the sending unit, and status says whether it sends (456.922556ms)
|
||||||
|
✔ uninstall and status take no --send (51.837069ms)
|
||||||
|
✔ install refuses while the unit is running, in any state but inactive or failed, and changes nothing (380.875828ms)
|
||||||
|
✔ when the state read fails, install and uninstall refuse with one line and change nothing (74.729467ms)
|
||||||
|
✔ install needs --copy naming a copy that exists, and refuses outside the canonical checkout without --allow-worktree (84.669621ms)
|
||||||
|
✔ install refuses unless the bus unit runs from the same copy, naming both paths, and changes nothing (842.958577ms)
|
||||||
|
✔ render --copy runs the copy and reads the queue in the checkout; without --copy the unit is unchanged (23.118673ms)
|
||||||
|
✔ the uid header takes only an Authentik uid; the username header still takes a username (357.678907ms)
|
||||||
|
✔ a preview value cannot start with - or . (30.161503ms)
|
||||||
|
✔ status names the copy and whether refactor has moved past it, and says when the unit runs from no copy (786.861399ms)
|
||||||
|
✔ tokens.css and icons.svg are byte copies of docs/design (0.842622ms)
|
||||||
|
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.569989ms)
|
||||||
|
✔ shipped strings are unchanged, and the web keeps its write boundary (1.927675ms)
|
||||||
|
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8023.13395ms)
|
||||||
|
✔ five states on Board, Inbox, Tasks, Agents and Trail (2574.890697ms)
|
||||||
|
✔ Queue, Business and Settings in their five states, with no move button (5356.981266ms)
|
||||||
|
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2725.729146ms)
|
||||||
|
✔ the server log names no config path when the system config refuses (314.37407ms)
|
||||||
|
ℹ tests 108
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 108
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 54610.2356
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
OK status with missing harness credential exits 3 and still lists accounts
|
||||||
|
OK status reports harness credential (read-only) + mosaic accounts
|
||||||
|
OK api key material never reaches output
|
||||||
|
OK oauth token material never reaches output
|
||||||
|
OK unparseable credential file exits 2
|
||||||
|
OK symlinked credential file exits 4
|
||||||
|
OK env-side credential names reported
|
||||||
|
OK env var values never reach output
|
||||||
|
OK accounts without an accounts dir reports none and creates nothing
|
||||||
|
OK accounts lists files and marks the active one
|
||||||
|
OK loose account perms flagged in listing
|
||||||
|
OK agent --auth with missing account file refuses (exit 4)
|
||||||
|
OK agent --auth with non-0600 account file refuses
|
||||||
|
OK agent --auth with invalid account name refuses
|
||||||
|
OK auth.sh without valid config refuses
|
||||||
|
|
||||||
|
selftest: 15 passed, 0 failed
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
Note: switching to '94ece491f05ecbb737227da8a7ba1d57a1ac75fd'.
|
||||||
|
|
||||||
|
You are in 'detached HEAD' state. You can look around, make experimental
|
||||||
|
changes and commit them, and you can discard any commits you make in this
|
||||||
|
state without impacting any branches by switching back to a branch.
|
||||||
|
|
||||||
|
If you want to create a new branch to retain commits you create, you may
|
||||||
|
do so (now or later) by using -c with the switch command. Example:
|
||||||
|
|
||||||
|
git switch -c <new-branch-name>
|
||||||
|
|
||||||
|
Or undo this operation with:
|
||||||
|
|
||||||
|
git switch -
|
||||||
|
|
||||||
|
Turn off this advice by setting config variable advice.detachedHead to false
|
||||||
|
|
||||||
|
Not currently on any branch.
|
||||||
|
nothing to commit, working tree clean
|
||||||
|
Note: switching to '94ece491f05ecbb737227da8a7ba1d57a1ac75fd'.
|
||||||
|
|
||||||
|
You are in 'detached HEAD' state. You can look around, make experimental
|
||||||
|
changes and commit them, and you can discard any commits you make in this
|
||||||
|
state without impacting any branches by switching back to a branch.
|
||||||
|
|
||||||
|
If you want to create a new branch to retain commits you create, you may
|
||||||
|
do so (now or later) by using -c with the switch command. Example:
|
||||||
|
|
||||||
|
git switch -c <new-branch-name>
|
||||||
|
|
||||||
|
Or undo this operation with:
|
||||||
|
|
||||||
|
git switch -
|
||||||
|
|
||||||
|
Turn off this advice by setting config variable advice.detachedHead to false
|
||||||
|
|
||||||
|
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
|
||||||
|
OK dry-run committed nothing
|
||||||
|
OK apply: allowed change exits 0 (exit 0)
|
||||||
|
OK apply: attribution in commit subject
|
||||||
|
OK apply: target tree clean after commit
|
||||||
|
OK disallowed path refused (exit 1)
|
||||||
|
OK disallowed path: target untouched
|
||||||
|
OK syntax gate refused broken .mjs (exit 1)
|
||||||
|
OK syntax gate: target untouched
|
||||||
|
OK suite failure refused (exit 1)
|
||||||
|
OK suite failure: target reverted to clean
|
||||||
|
OK disabled policy refused (exit 2)
|
||||||
|
OK disabled policy: target untouched
|
||||||
|
OK failed run refused (exit 1)
|
||||||
|
OK failed run: target untouched
|
||||||
|
OK missing run exits 4 (exit 4)
|
||||||
|
OK invalid policy exits 2 (exit 2)
|
||||||
|
|
||||||
|
selftest: 17 passed, 0 failed
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
OK absent adapter defaults to pi
|
||||||
|
OK adapter mock validates (exit 0)
|
||||||
|
OK unsupported adapter exits 2 (exit 2)
|
||||||
|
OK env exports adapter
|
||||||
|
OK bootstrap creates default when absent (exit 0)
|
||||||
|
OK bootstrap wrote config file
|
||||||
|
OK bootstrap is idempotent on existing config (exit 0)
|
||||||
|
OK bootstrap did not rewrite existing config
|
||||||
|
OK validate missing config exits 3 (exit 3)
|
||||||
|
OK malformed JSON exits 2 (exit 2)
|
||||||
|
OK unsupported configVersion exits 2 (exit 2)
|
||||||
|
OK unknown top-level key exits 2 (exit 2)
|
||||||
|
OK unknown execution key exits 2 (exit 2)
|
||||||
|
OK unsupported backend exits 2 (exit 2)
|
||||||
|
OK unsupported environment exits 2 (exit 2)
|
||||||
|
OK relative dataRoot exits 2 (exit 2)
|
||||||
|
OK non-canonical dataRoot exits 2 (exit 2)
|
||||||
|
OK filesystem root dataRoot exits 2 (exit 2)
|
||||||
|
OK home directory dataRoot exits 2 (exit 2)
|
||||||
|
OK dataRoot containing config dir exits 2 (exit 2)
|
||||||
|
OK control character in provider exits 2 (exit 2)
|
||||||
|
OK symlinked config file exits 2 (exit 2)
|
||||||
|
OK env exports resolve correctly
|
||||||
|
OK failed validation modified nothing
|
||||||
|
|
||||||
|
selftest: 24 passed, 0 failed
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
toolchain: node v26.8.1
|
||||||
|
|
||||||
|
OK syntax: packages/discord/src/approvals.mjs
|
||||||
|
OK syntax: packages/discord/src/authorize.mjs
|
||||||
|
OK syntax: packages/discord/src/binding.mjs
|
||||||
|
OK syntax: packages/discord/src/cli.mjs
|
||||||
|
OK syntax: packages/discord/src/connector.mjs
|
||||||
|
OK syntax: packages/discord/src/context.mjs
|
||||||
|
OK syntax: packages/discord/src/engine-pi.mjs
|
||||||
|
OK syntax: packages/discord/src/errors.mjs
|
||||||
|
OK syntax: packages/discord/src/gateway.mjs
|
||||||
|
OK syntax: packages/discord/src/git.mjs
|
||||||
|
OK syntax: packages/discord/src/journal.mjs
|
||||||
|
OK syntax: packages/discord/src/notify.mjs
|
||||||
|
OK syntax: packages/discord/src/rest.mjs
|
||||||
|
OK syntax: packages/discord/src/setspark.mjs
|
||||||
|
OK syntax: packages/discord/src/tools.mjs
|
||||||
|
OK syntax: packages/discord/src/web.mjs
|
||||||
|
OK syntax: packages/discord/bin/git-credential.mjs
|
||||||
|
OK syntax: packages/discord/extension/tools.mjs
|
||||||
|
OK syntax: packages/discord/tests/approvals.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/authorize.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/binding.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/connector.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/context.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/engine.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/fake-pi.mjs
|
||||||
|
OK syntax: packages/discord/tests/gateway.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/git.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/helpers.mjs
|
||||||
|
OK syntax: packages/discord/tests/journal.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/notify.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/recover.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/rest.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/setspark.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/tools.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/web.test.mjs
|
||||||
|
OK syntax: packages/discord/fixtures/claim-worker.mjs
|
||||||
|
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
|
||||||
|
OK syntax: scripts/discord.sh
|
||||||
|
OK syntax: scripts/discord-service.sh
|
||||||
|
OK packages/discord declares no dependencies
|
||||||
|
OK no bot-token-shaped string in packages/discord
|
||||||
|
OK fixture binding uses placeholder ids only
|
||||||
|
OK fixture binding validates
|
||||||
|
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
|
||||||
|
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
|
||||||
|
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
|
||||||
|
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
|
||||||
|
OK real pi with protocol vault adds reserve_id to the git verbs
|
||||||
|
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
|
||||||
|
OK real pi refuses a git key on a read-only root (fail closed)
|
||||||
|
OK real pi with the pilot flags (--no-tools) exposes no tool at all
|
||||||
|
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
|
||||||
|
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
|
||||||
|
OK node --test packages/discord/tests/ (ℹ pass 178)
|
||||||
|
OK scripts/discord.sh --help exits 0
|
||||||
|
OK scripts/discord.sh check without a binding exits 4
|
||||||
|
OK scripts/discord.sh recover without a binding exits 4
|
||||||
|
OK scripts/discord.sh reload without a binding exits 4
|
||||||
|
OK scripts/discord-service.sh without a command exits 4
|
||||||
|
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
|
||||||
|
OK service install writes the rendered unit (0644) and leaves no temp file
|
||||||
|
OK service install a second time reports unchanged
|
||||||
|
OK systemd-analyze verify accepts the rendered unit
|
||||||
|
OK service uninstall removes the unit file
|
||||||
|
OK service install with an unknown flag exits 4
|
||||||
|
OK service install with USER unset finishes and names the account for lingering
|
||||||
|
|
||||||
|
discord suite: 66 passed, 0 failed
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
OK initial ordinary-file install
|
||||||
|
OK installed tree matches canonical source
|
||||||
|
OK installed tree has no symlinks
|
||||||
|
OK check detects installation drift
|
||||||
|
OK sync refuses to overwrite installation drift
|
||||||
|
OK check detects an extra destination file
|
||||||
|
OK check detects an extra destination directory
|
||||||
|
OK check rejects a destination symlink
|
||||||
|
OK sync accepts a canonical source update
|
||||||
|
OK updated installation matches canonical source
|
||||||
|
scripts/test-extension-package.sh: line 14: 2478285 Killed "$@" > /dev/null 2>&1
|
||||||
|
OK forced interruption kills the replacing process
|
||||||
|
OK next invocation recovers old consistent installation
|
||||||
|
OK interrupted replacement rolled back
|
||||||
|
OK sync succeeds after interruption recovery
|
||||||
|
OK unlocked stale lock file does not block
|
||||||
|
OK active lock refuses a concurrent sync
|
||||||
|
OK source symlink fails closed
|
||||||
|
OK nested second entrypoint fails closed
|
||||||
|
|
||||||
|
extension package selftest: 18 passed, 0 failed
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0
|
||||||
|
|
||||||
|
OK syntax: scripts/foundation-inspect.mjs
|
||||||
|
OK syntax: scripts/foundation/strict-json.mjs
|
||||||
|
OK syntax: scripts/foundation/canonical.mjs
|
||||||
|
OK syntax: scripts/foundation/resolve.mjs
|
||||||
|
OK syntax: scripts/foundation/validate-record.mjs
|
||||||
|
OK syntax: scripts/foundation/fixtures/build-fixtures.mjs
|
||||||
|
OK syntax: scripts/foundation/canonical.test.mjs
|
||||||
|
OK syntax: scripts/foundation/cli.test.mjs
|
||||||
|
OK syntax: scripts/foundation/fixtures.test.mjs
|
||||||
|
OK syntax: scripts/foundation/resolve.test.mjs
|
||||||
|
OK syntax: scripts/foundation/strict-json.test.mjs
|
||||||
|
OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)
|
||||||
|
OK fixture generator runs
|
||||||
|
OK checked-in fixtures/bundles equal a fresh generation
|
||||||
|
OK checked-in fixtures/raw equal a fresh generation
|
||||||
|
OK checked-in fixtures/index.json equal a fresh generation
|
||||||
|
OK checked-in demo bundles equal a fresh generation
|
||||||
|
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
|
||||||
|
OK node --test scripts/foundation/ (ℹ pass 80)
|
||||||
|
OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)
|
||||||
|
platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999)
|
||||||
|
node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases)
|
||||||
|
schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
|
||||||
|
profile column (schema-valid records only): profile-valid 510, profile-invalid 30
|
||||||
|
profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns
|
||||||
|
OK oracle: zero schema-column disagreements with the pinned checker
|
||||||
|
OK oracle: strict-only profile refusals are counted and asserted
|
||||||
|
OK demo: permitted read preview exits 0 (exit 0)
|
||||||
|
OK demo: permitted file.change preview exits 0 (exit 0)
|
||||||
|
OK demo: assignment.change proposal is unresolved (exit 3) (exit 3)
|
||||||
|
OK demo: revoked registration is refused (exit 3) (exit 3)
|
||||||
|
OK demo: message is not authority (exit 3) (exit 3)
|
||||||
|
OK usage: no arguments exits 2 (exit 2)
|
||||||
|
OK io: missing file exits 4 (exit 4)
|
||||||
|
OK io: directory exits 4 (exit 4)
|
||||||
|
OK io: symlink exits 4 (O_NOFOLLOW) (exit 4)
|
||||||
|
OK bound: oversize fixture exits 2 (exit 2)
|
||||||
|
OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2)
|
||||||
|
OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2)
|
||||||
|
OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0)
|
||||||
|
OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed
|
||||||
|
OK text output starts with the disclaimer
|
||||||
|
OK json output is valid JSON with result allowed and exactly the charter §7 fields
|
||||||
|
OK json golden matches byte-for-byte
|
||||||
|
OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal
|
||||||
|
OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs
|
||||||
|
OK canary never printed (bundle run and credential-file run)
|
||||||
|
OK a non-bundle JSON file is refused at the shape gate, not read into output
|
||||||
|
OK no field of the non-bundle file is echoed
|
||||||
|
|
||||||
|
selftest: 44 passed, 0 failed
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
toolchain: node v26.8.1, git version 2.55.0
|
||||||
|
|
||||||
|
OK syntax: packages/queue/src/cli.mjs
|
||||||
|
OK syntax: packages/queue/src/errors.mjs
|
||||||
|
OK syntax: packages/queue/src/io.mjs
|
||||||
|
OK syntax: packages/queue/src/lock.mjs
|
||||||
|
OK syntax: packages/queue/src/queue.mjs
|
||||||
|
OK syntax: packages/queue/src/review.mjs
|
||||||
|
OK syntax: packages/queue/src/store.mjs
|
||||||
|
OK syntax: packages/queue/tests/commit.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/data.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/dispatch.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/gate-owner.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/helpers.mjs
|
||||||
|
OK syntax: packages/queue/tests/lock.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/migration.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/review.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/store.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/write.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
|
||||||
|
OK syntax: scripts/queue-commit.sh
|
||||||
|
OK syntax: scripts/git-hooks/pre-commit
|
||||||
|
OK syntax: scripts/mosaic
|
||||||
|
OK queue-commit.sh, the guard and scripts/mosaic are executable
|
||||||
|
OK packages/queue declares no dependencies
|
||||||
|
ℹ tests 156
|
||||||
|
ℹ pass 156
|
||||||
|
ℹ fail 0
|
||||||
|
OK node --test packages/queue/tests/
|
||||||
|
OK scripts/mosaic queue help
|
||||||
|
skip queue verify and render --check: this checkout (/home/jwoltje/darkwing-scratch/r115/wt) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
|
||||||
|
|
||||||
|
queue suite: 28 passed, 0 failed
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
OK valid RELEASE resolves (exit 0)
|
||||||
|
OK invalid RELEASE exits 1 (exit 1)
|
||||||
|
OK missing RELEASE exits 1 (exit 1)
|
||||||
|
OK valid RELEASE leaves image tag consistent with version
|
||||||
|
OK status safe on empty state (exit 0)
|
||||||
|
OK status created no pointer
|
||||||
|
OK fault-injected activation refuses (exit 1)
|
||||||
|
OK refused activation wrote no pointer
|
||||||
|
OK refusal logged exactly once with valid fields
|
||||||
|
OK healthy activation succeeds (exit 0)
|
||||||
|
OK pointer written with valid fields
|
||||||
|
OK repeat activation succeeds (log grows) (exit 0)
|
||||||
|
OK log is append-only across activations
|
||||||
|
OK rollback without previous refuses (exit 1)
|
||||||
|
|
||||||
|
selftest: 14 passed, 0 failed
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
OK valid task validates (exit 0)
|
||||||
|
OK unknown task key exits 2 (exit 2)
|
||||||
|
OK unsupported taskVersion exits 2 (exit 2)
|
||||||
|
OK invalid task id exits 2 (exit 2)
|
||||||
|
OK empty prompt exits 2 (exit 2)
|
||||||
|
OK NUL in expectExact exits 2 (exit 2)
|
||||||
|
OK out-of-range timeout exits 2 (exit 2)
|
||||||
|
OK missing mission file exits 4 (exit 4)
|
||||||
|
OK task with valid mission validates (exit 0)
|
||||||
|
OK invalid mission exits 2 (exit 2)
|
||||||
|
OK validate missing task exits 4 (exit 4)
|
||||||
|
OK validation does not modify the task file
|
||||||
|
OK prune dry-run exits 0 (exit 0)
|
||||||
|
OK dry-run deleted nothing
|
||||||
|
OK prune --keep=2 --yes removes oldest (exit 0)
|
||||||
|
OK kept exactly 2 newest runs
|
||||||
|
OK newest run kept, oldest pruned
|
||||||
|
OK append-only receipt written (3 entries)
|
||||||
|
OK sessions/workspaces untouched by prune
|
||||||
|
OK prune with invalid keep exits 4 (exit 4)
|
||||||
|
OK mock adapter: gate passes on matching mock response (exit 0)
|
||||||
|
OK mock adapter: expect-mismatch recorded (exit 1)
|
||||||
|
OK mismatch reason recorded
|
||||||
|
OK unknown adapter fails closed (exit 1)
|
||||||
|
OK mission task runs via mock adapter (exit 0)
|
||||||
|
OK mission section injected into generated prompt
|
||||||
|
OK retry of mission run succeeds (exit 0)
|
||||||
|
OK retriedFrom lineage recorded
|
||||||
|
OK mission section present after retry (relative path resolved)
|
||||||
|
OK retry of missing run exits 4 (exit 4)
|
||||||
|
OK skill install bundled ms-tools (exit 0)
|
||||||
|
OK installed to skills-available
|
||||||
|
OK double install refuses (exit 1)
|
||||||
|
OK activate enables skill (exit 0)
|
||||||
|
OK enabled dir populated
|
||||||
|
OK uninstall while enabled refuses (exit 1)
|
||||||
|
OK deactivate moves back to available (exit 0)
|
||||||
|
OK uninstall removes from available (exit 0)
|
||||||
|
OK seat with enabled skill launches
|
||||||
|
OK skill path delivered to adapter
|
||||||
|
OK shipped researcher contract resolves (ceiling + network)
|
||||||
|
OK resolve-role refuses a non-role document (conductor policy) (exit 2)
|
||||||
|
OK resolve-role refuses name/filename mismatch (exit 2)
|
||||||
|
OK resolve-role refuses unknown network declaration (exit 2)
|
||||||
|
OK resolve-role refuses duplicate role tool (exit 2)
|
||||||
|
OK resolve-role refuses unsupported tool (exit 2)
|
||||||
|
OK resolve-role refuses missing contract file (exit 4)
|
||||||
|
OK version 1 role prints no contract line
|
||||||
|
OK shipped version 2 roles resolve with their contracts (pm, cto, coder, reviewer)
|
||||||
|
OK resolve-role accepts a minimal version 2 role (exit 0)
|
||||||
|
OK resolve-role refuses an action outside the vocabulary (exit 2)
|
||||||
|
OK resolve-role refuses a gated-only action in a role file (exit 2)
|
||||||
|
OK resolve-role refuses a Vikunja verb no role may hold (exit 2)
|
||||||
|
OK resolve-role refuses a version 2 role whose contract is missing (exit 2)
|
||||||
|
OK resolve-role refuses a contract that is a symbolic link (exit 2)
|
||||||
|
OK seat launches under role ceiling
|
||||||
|
OK role ceiling narrows seat tools (read,write,bash -> read,bash)
|
||||||
|
OK narrowing recorded loudly
|
||||||
|
OK missing role contract refuses launch (exit 2)
|
||||||
|
OK missing-contract refusal names the role
|
||||||
|
OK empty ceiling intersection -> tool-free seat
|
||||||
|
OK tool-free outcome recorded loudly
|
||||||
|
OK overridden agents dir without seat definition refuses (exit 4)
|
||||||
|
OK seat-resolution refusal names the seat
|
||||||
|
OK policy: mission only -> mission tools
|
||||||
|
OK policy: task only -> task tools
|
||||||
|
OK policy: both -> intersection (task narrowed)
|
||||||
|
OK policy: empty intersection -> tool-free
|
||||||
|
OK invalid mission capabilities rejected (exit 2)
|
||||||
|
mosaic-task: unsupported mission tool: "sudo" (supported: read, write, edit, bash, grep, find, ls)
|
||||||
|
OK task run with user layer present (exit 0)
|
||||||
|
OK user context dispatched into generated prompt
|
||||||
|
OK workspace+tools task runs via mock (exit 0)
|
||||||
|
OK workspace path + tools delivered to adapter
|
||||||
|
OK persistent workspace created on host
|
||||||
|
OK plain task still runs (no workspace/tools) (exit 0)
|
||||||
|
OK workspace var present but empty when absent
|
||||||
|
OK tools empty when absent
|
||||||
|
OK unknown tool exits 2 (exit 2)
|
||||||
|
OK workspace traversal exits 2 (exit 2)
|
||||||
|
OK live hello task succeeds with exact marker
|
||||||
|
OK result.json written in run dir
|
||||||
|
OK result.json contents are correct
|
||||||
|
OK wrong expectExact fails with exit 1 (reason: expect-mismatch)
|
||||||
|
OK each run gets a distinct run dir (no clobber)
|
||||||
|
OK list shows both runs
|
||||||
|
OK fork base: teach succeeds (exit 0)
|
||||||
|
OK fork child recalls ancestor context (exit 0)
|
||||||
|
OK forked child recalled ancestor code word
|
||||||
|
OK ancestor session untouched by fork
|
||||||
|
OK child session dir has its own branch file
|
||||||
|
OK onboard without name exits 4 (non-interactive) (exit 4)
|
||||||
|
OK onboard --name renders profile (exit 0)
|
||||||
|
OK profile written
|
||||||
|
OK canon structure: required filled, optional placeholdered
|
||||||
|
OK canon sections present
|
||||||
|
OK user recall run succeeds
|
||||||
|
OK recalled user name (response: Jason)
|
||||||
|
OK no agent identity on headless run
|
||||||
|
|
||||||
|
selftest: 98 passed, 0 failed
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
mutant m1 rc=1 ℹ pass 148 ℹ fail 8 ℹ skipped 0 restored=True
|
||||||
|
✖ a cross-role decision resolved by its arbiter comes from that role and run, with no request (225.174299ms)
|
||||||
|
✖ a human DECISION send keeps its own instruction as the request, not a close's answer (257.647089ms)
|
||||||
|
✖ a human resolve sends the raising role one message from the human, and its request is the answer (218.710281ms)
|
||||||
|
✖ a human seen before the close leaves the request on the close's answer, not the seen (259.745164ms)
|
||||||
|
✖ a human withdraw sends one message with the note (226.660927ms)
|
||||||
|
✖ a raising role nobody holds at the close gets the message on the next claim (221.960203ms)
|
||||||
|
✖ message.send consumes approval and prevents a later send or authorize (166.862165ms)
|
||||||
|
✖ refused closes send nothing: invalid-choice, decision-closed, not-resolver (258.390606ms)
|
||||||
|
KILLED
|
||||||
|
mutant m2 rc=1 ℹ pass 149 ℹ fail 7 ℹ skipped 0 restored=True
|
||||||
|
✖ a cross-role decision resolved by its arbiter comes from that role and run, with no request (321.036389ms)
|
||||||
|
✖ a human DECISION send keeps its own instruction as the request, not a close's answer (294.358773ms)
|
||||||
|
✖ a human resolve sends the raising role one message from the human, and its request is the answer (238.003301ms)
|
||||||
|
✖ a human seen before the close leaves the request on the close's answer, not the seen (318.362764ms)
|
||||||
|
✖ a human withdraw sends one message with the note (355.779839ms)
|
||||||
|
✖ a raising role nobody holds at the close gets the message on the next claim (280.815533ms)
|
||||||
|
✖ refused closes send nothing: invalid-choice, decision-closed, not-resolver (399.523409ms)
|
||||||
|
KILLED
|
||||||
|
mutant m3 rc=1 ℹ pass 155 ℹ fail 1 ℹ skipped 0 restored=True
|
||||||
|
✖ the raising run's withdraw, a supersede, a within-role raise and seen send nothing (344.385709ms)
|
||||||
|
KILLED
|
||||||
|
mutant m4 rc=1 ℹ pass 155 ℹ fail 1 ℹ skipped 0 restored=True
|
||||||
|
✖ a human seen before the close leaves the request on the close's answer, not the seen (373.888643ms)
|
||||||
|
KILLED
|
||||||
Executable
+60
@@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Row 115 (#1609): the brief's three mutants of the decision close message.
|
||||||
|
# m1 drops the message: the close block never runs.
|
||||||
|
# m2 addresses the message to route_to instead of the raising role.
|
||||||
|
# m3 drops the raising-run check, so the raiser's own withdraw sends one too.
|
||||||
|
# Each runs the FULL bus suite (node --test tests/*.test.mjs in packages/bus)
|
||||||
|
# and restores the source from the .orig copy whatever happens.
|
||||||
|
# Usage: mutants.py <worktree> <log dir>
|
||||||
|
import os, shutil, subprocess, sys, signal
|
||||||
|
|
||||||
|
WT, LOGS = sys.argv[1], sys.argv[2]
|
||||||
|
MUTANTS = {
|
||||||
|
'm1': ('packages/bus/src/broker.mjs',
|
||||||
|
" if (op !== 'seen' && (s.human || s.run !== d.raised_by_run)) {\n",
|
||||||
|
" if (false) {\n"),
|
||||||
|
'm2': ('packages/bus/src/broker.mjs',
|
||||||
|
" d.raised_by_role,\n 'DECISION',\n",
|
||||||
|
" d.route_to,\n 'DECISION',\n"),
|
||||||
|
'm3': ('packages/bus/src/broker.mjs',
|
||||||
|
" if (op !== 'seen' && (s.human || s.run !== d.raised_by_run)) {\n",
|
||||||
|
" if (op !== 'seen') {\n"),
|
||||||
|
# Filbert's f4 in round 1: the fallback loses its op filter, so a human seen's answer can win.
|
||||||
|
'm4': ('packages/bus/src/broker.mjs',
|
||||||
|
" AND json_extract(body,'$.op') IN ('resolved','withdrawn') ORDER BY seq LIMIT 1\",",
|
||||||
|
" ORDER BY seq LIMIT 1\","),
|
||||||
|
}
|
||||||
|
os.makedirs(LOGS, exist_ok=True)
|
||||||
|
env = dict(os.environ, TMPDIR=os.path.expanduser('~/darkwing-scratch/r115/tmp'))
|
||||||
|
for name, (rel, cur, new) in MUTANTS.items():
|
||||||
|
src = os.path.join(WT, rel)
|
||||||
|
text = open(src).read()
|
||||||
|
if text.count(cur) != 1:
|
||||||
|
sys.exit(f'STOP {name}: the target text in {rel} is not unique or has moved')
|
||||||
|
if os.path.exists(src + '.orig'):
|
||||||
|
sys.exit(f'STOP {rel}.orig already exists')
|
||||||
|
log_path = os.path.join(LOGS, f'mutant-{name}.log')
|
||||||
|
shutil.copy2(src, src + '.orig')
|
||||||
|
try:
|
||||||
|
open(src, 'w').write(text.replace(cur, new))
|
||||||
|
with open(log_path, 'w') as out:
|
||||||
|
p = subprocess.Popen('node --test tests/*.test.mjs', shell=True, cwd=os.path.join(WT, 'packages/bus'),
|
||||||
|
stdout=out, stderr=subprocess.STDOUT, env=env, start_new_session=True)
|
||||||
|
try:
|
||||||
|
rc = p.wait(timeout=600)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
os.killpg(p.pid, signal.SIGKILL)
|
||||||
|
p.wait()
|
||||||
|
rc = 'timeout'
|
||||||
|
finally:
|
||||||
|
shutil.move(src + '.orig', src)
|
||||||
|
restored = open(src).read() == text
|
||||||
|
log = open(log_path).read()
|
||||||
|
counts = [l for l in log.splitlines() if l.startswith(('ℹ pass', 'ℹ fail', 'ℹ skipped'))]
|
||||||
|
failed = [l.strip() for l in log.splitlines() if l.lstrip().startswith('✖') and 'failing tests' not in l]
|
||||||
|
print(f'mutant {name} rc={rc} {" ".join(counts)} restored={restored}')
|
||||||
|
for f in sorted(set(failed)):
|
||||||
|
print(' ' + f)
|
||||||
|
print('KILLED' if rc != 0 else 'SURVIVED')
|
||||||
|
if not restored:
|
||||||
|
sys.exit(f'STOP {rel} was not restored')
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (146.439611ms)
|
||||||
|
✔ agents: working counts what the holder received and has not read; routed alone is not working (204.492598ms)
|
||||||
|
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (335.491715ms)
|
||||||
|
✔ agents: working wins over waiting, and Input needed still shows (159.213144ms)
|
||||||
|
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (200.760347ms)
|
||||||
|
✔ agents: state reads only its own business, even for a run name both use (202.73579ms)
|
||||||
|
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (216.093851ms)
|
||||||
|
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (198.585432ms)
|
||||||
|
✔ agents: stranded is oldest first and capped at 50 for a role (860.508105ms)
|
||||||
|
✔ agents: stranded is per role and per business (324.815969ms)
|
||||||
|
✔ agents: a message routed to a held role and not yet received is not stranded (114.127787ms)
|
||||||
|
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (171.790316ms)
|
||||||
|
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (264.039045ms)
|
||||||
|
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (277.255023ms)
|
||||||
|
✔ launch events require a human CLI capability; generic emit cannot forge authority events (165.33637ms)
|
||||||
|
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (188.427232ms)
|
||||||
|
✔ a recommendation that is not exactly one option key refuses invalid-recommendation after the options (272.554293ms)
|
||||||
|
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (176.832546ms)
|
||||||
|
✔ task action subjects and linked decision trail are complete and ordered (190.12181ms)
|
||||||
|
✔ launch binding is durable and reconnecting requires the identical trusted record (127.186335ms)
|
||||||
|
✔ business isolation includes inherited object names and cross-business message references (203.100333ms)
|
||||||
|
✔ authority never transfers between action, run, target, unresolved or replaced role holder (275.791149ms)
|
||||||
|
✔ task projection uses schema current view, skipping earlier and equal-start polls (134.307523ms)
|
||||||
|
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (218.503985ms)
|
||||||
|
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (138.691308ms)
|
||||||
|
✔ both arbiters require human resolution when their cross-role route is themselves (209.023032ms)
|
||||||
|
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (128.60659ms)
|
||||||
|
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.089093ms)
|
||||||
|
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (102.644105ms)
|
||||||
|
✔ credentials refuses a role session and any argument (175.421011ms)
|
||||||
|
✔ credentials reads the status on each request, and a broker without one lists none (217.652898ms)
|
||||||
|
✔ the broker process answers credentials without a token, path or variable in its output, log or store (184.083974ms)
|
||||||
|
✔ only validated broker references load; returned data and exceptions cannot expose a known token (3.8366ms)
|
||||||
|
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (5.716255ms)
|
||||||
|
✔ expiry refuses use and env references never become client data (0.649629ms)
|
||||||
|
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.265295ms)
|
||||||
|
✔ opaque tokens shorter than 16 characters refuse before use (0.254835ms)
|
||||||
|
✖ a human resolve sends the raising role one message from the human, and its request is the answer (218.710281ms)
|
||||||
|
✖ a cross-role decision resolved by its arbiter comes from that role and run, with no request (225.174299ms)
|
||||||
|
✖ a human withdraw sends one message with the note (226.660927ms)
|
||||||
|
✖ a human seen before the close leaves the request on the close's answer, not the seen (259.745164ms)
|
||||||
|
✔ the raising run's withdraw, a supersede, a within-role raise and seen send nothing (295.85737ms)
|
||||||
|
✖ refused closes send nothing: invalid-choice, decision-closed, not-resolver (258.390606ms)
|
||||||
|
✖ a raising role nobody holds at the close gets the message on the next claim (221.960203ms)
|
||||||
|
✖ a human DECISION send keeps its own instruction as the request, not a close's answer (257.647089ms)
|
||||||
|
✔ roles lists open decisions routed to a role, and never the human inbox (222.720858ms)
|
||||||
|
✔ recent lists closed decisions of every route, newest close first, with how each closed (287.030943ms)
|
||||||
|
✔ recent gives the closing event as closed when the decision was seen first (291.294591ms)
|
||||||
|
✔ recent stops at RECENT rows (797.795486ms)
|
||||||
|
✔ every row is what trail already gives the same session for that id (264.064166ms)
|
||||||
|
✔ a role session can't list decisions, so it can't read another role's open decisions (412.886147ms)
|
||||||
|
✔ decisions is a read: one business, strict arguments, nothing written (363.27407ms)
|
||||||
|
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (127.832556ms)
|
||||||
|
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (152.836966ms)
|
||||||
|
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (154.817603ms)
|
||||||
|
✔ endLaunch leaves a claim another run took alone (173.345465ms)
|
||||||
|
✔ refuse records action.refused against the caller with the code only (123.913481ms)
|
||||||
|
✔ launches off refuses role.launch with launch-revoked until launches on (189.092258ms)
|
||||||
|
✔ broker process: launch ops authorize role.launch, record refusals and end runs (265.243611ms)
|
||||||
|
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (1.524281ms)
|
||||||
|
✔ process reader gets own kernel identity without exposing environment values (0.463488ms)
|
||||||
|
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.608609ms)
|
||||||
|
✔ real pid 1 remains inspectable when its environment is protected (0.257682ms)
|
||||||
|
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (202.022404ms)
|
||||||
|
✔ missing and foreign-business citations refuse and roll back message and grant (184.222698ms)
|
||||||
|
✔ cross-role sends still need a matching resolved decision and consume it once (254.827475ms)
|
||||||
|
✔ messages pages all human messages newest first without writes; read state is history (2528.736534ms)
|
||||||
|
✔ messages refuses role callers, invalid paging and cursors outside this human feed (248.835433ms)
|
||||||
|
✔ views messages passes only its paging arguments through (0.267113ms)
|
||||||
|
✔ messages isolates businesses even with a valid foreign cursor (150.479728ms)
|
||||||
|
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (184.983356ms)
|
||||||
|
✔ startup token refusal returns safe code without value or partial listening broker (35.542136ms)
|
||||||
|
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (173.873281ms)
|
||||||
|
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (175.549192ms)
|
||||||
|
✔ trusted host registers later launches; socket clients never have a registration verb (175.746331ms)
|
||||||
|
✔ runtime excludes declared project roots even when host supplies no repoRoots (34.791221ms)
|
||||||
|
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (174.455408ms)
|
||||||
|
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (239.745344ms)
|
||||||
|
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (200.885515ms)
|
||||||
|
✔ a message with no reply gives an empty list (167.926092ms)
|
||||||
|
✔ replies come oldest first and stop at REPLIES (753.41429ms)
|
||||||
|
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (288.006531ms)
|
||||||
|
✔ strict arguments; a role session gets human-required (375.154798ms)
|
||||||
|
✔ views passes replies through as one call with the id (0.251637ms)
|
||||||
|
✔ requirements returns the committed list to a holding role session, and no one else (212.141277ms)
|
||||||
|
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (145.731496ms)
|
||||||
|
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (185.119021ms)
|
||||||
|
✔ a push is recorded as the action.allowed for git.push.working on the task, and only the host records one (234.856599ms)
|
||||||
|
✔ review.request names the latest pushed commit and sends the reviewer a REVIEW-REQUEST (251.441334ms)
|
||||||
|
✔ review.request says which run holds the reviewer role, or null, and a null request waits for a claim (285.178954ms)
|
||||||
|
✔ review.request refuses a commit that isn't the latest push, a non-reviewer and a role without the action (371.533068ms)
|
||||||
|
✔ reviewFetch answers only the addressed reviewer, for the request it cites (253.864051ms)
|
||||||
|
✔ review.verdict answers one request once, to the role that asked (296.682331ms)
|
||||||
|
✔ task.close on a pushed task needs an approve verdict for the latest push (382.887ms)
|
||||||
|
✔ a refusal the host decided is filed on the task it names, and only a task ref counts (243.540689ms)
|
||||||
|
✔ v3b prototype refusals, views and append-only mutations (1059.678553ms)
|
||||||
|
✔ gated approval authorizes once, survives store reopen, and fresh approval works (267.015335ms)
|
||||||
|
✔ another run cannot consume an approval; a failed check leaves it usable (223.274653ms)
|
||||||
|
✔ two scheduled callers have exactly one grant and one consumed refusal (169.47892ms)
|
||||||
|
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (306.264723ms)
|
||||||
|
✔ class drift gated to cross-role refuses before consumption (248.915225ms)
|
||||||
|
✔ class drift cross-role to gated refuses before consumption (254.258242ms)
|
||||||
|
✔ class drift gated to within-role refuses before consumption (206.473148ms)
|
||||||
|
✔ class drift cross-role to within-role refuses before consumption (263.139291ms)
|
||||||
|
✔ class drift within-role to gated refuses before consumption (212.763259ms)
|
||||||
|
✔ class drift within-role to cross-role refuses before consumption (203.245764ms)
|
||||||
|
✖ message.send consumes approval and prevents a later send or authorize (166.862165ms)
|
||||||
|
✔ role.revoke consumes approval and prevents a later revoke or authorize (245.574662ms)
|
||||||
|
✔ a business that never switched launches reads allowed with no time and no event (144.253358ms)
|
||||||
|
✔ launches revoked and then restored read allowed, with the restore time and event id (166.679966ms)
|
||||||
|
✔ a task closed on the tracker leaves the open list, and total counts the open ones (160.174952ms)
|
||||||
|
✔ a tombstone is not open (141.500093ms)
|
||||||
|
✔ the open list stops at the cap and says so; total still counts every open task (138.627244ms)
|
||||||
|
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (147.43308ms)
|
||||||
|
✔ a human session reads the same state, with the same cap (152.736687ms)
|
||||||
|
✔ reading state writes nothing: every table keeps its row count (154.726928ms)
|
||||||
|
✔ creates private WAL store and excludes a second writer until explicit close (127.481623ms)
|
||||||
|
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (160.322373ms)
|
||||||
|
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (194.696391ms)
|
||||||
|
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (174.410753ms)
|
||||||
|
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (108.447347ms)
|
||||||
|
✔ async transactions refuse before invoking their function (104.392869ms)
|
||||||
|
✔ the session default fits under the reply cap and pages to the end with next (270.110122ms)
|
||||||
|
✔ the short form: no description, assignees by role, every listed key (266.674886ms)
|
||||||
|
✔ each filter, alone and together, and total counts every match (312.652618ms)
|
||||||
|
✔ task_ref reads one task in full, as the whole board has it (283.046189ms)
|
||||||
|
✔ no arguments is the read the Console and mosaic make, unchanged (252.452429ms)
|
||||||
|
✔ an unknown argument or a bad value refuses invalid-request (909.794752ms)
|
||||||
|
✔ tasks rows carry priority and requirement for every session that reads tasks (301.224434ms)
|
||||||
|
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (339.290858ms)
|
||||||
|
✔ an empty business and one with snapshots but no task.created read cleanly (124.319959ms)
|
||||||
|
✔ recordTask keeps sync reads and a role write apart (182.348674ms)
|
||||||
|
✔ read_at must be one canonical UTC format, so the projection compares strings safely (116.990655ms)
|
||||||
|
✔ a bad entry refuses the whole record (104.311039ms)
|
||||||
|
✔ taskView reads the projection for one business (132.812579ms)
|
||||||
|
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (217.339984ms)
|
||||||
|
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (416.627587ms)
|
||||||
|
✔ without an adapter the server refuses every task verb (216.771735ms)
|
||||||
|
✔ the runtime refuses an invalid adapter and closes a valid one (205.652811ms)
|
||||||
|
✔ the process loads the S3 adapter from plain-data trackers (254.966563ms)
|
||||||
|
✔ the tester binds only when the business sets bus.tester to exactly true (807.940942ms)
|
||||||
|
✔ a business may not name a role tester, so the tester address is never a role (235.374329ms)
|
||||||
|
✔ every verb outside the tester list refuses tester-refused and is recorded against the tester (514.090215ms)
|
||||||
|
✔ a tester message is from tester, starts [mos test], and writes no human.input or authority (205.472921ms)
|
||||||
|
✔ a role answers a tester only in reply to a tester message, and the answer never reaches the human (213.064014ms)
|
||||||
|
✔ the tester reads the human views a reader reads (91.118023ms)
|
||||||
|
✔ tester proof binds its own CLI, start time and nonce, refuses a managed run and allows an agent (1.078932ms)
|
||||||
|
✔ neither CLI's nonce proves the other: each proof is bound to its own CLI and nonce variable (0.331067ms)
|
||||||
|
✔ testerNonce reads only the tester's variable and gives null on any read error (0.203538ms)
|
||||||
|
✔ an envelope carries exactly one of cap, human and tester; no tester authenticator refuses (55.527465ms)
|
||||||
|
✔ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (699.341512ms)
|
||||||
|
✔ socket capability stamps launch identity; shared views use wire, no SQL client (158.549433ms)
|
||||||
|
✔ two wire claims serialize; a lost reply never automatically retries (178.379356ms)
|
||||||
|
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (104.076273ms)
|
||||||
|
✔ client preserves UTF-8 when a response divides a multibyte character (12.078422ms)
|
||||||
|
✔ committed mutation followed by dropped reply reports unknown and is never retried (152.110081ms)
|
||||||
|
ℹ tests 156
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 148
|
||||||
|
ℹ fail 8
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 4044.769509
|
||||||
|
|
||||||
|
✖ failing tests:
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:59:1
|
||||||
|
✖ a human resolve sends the raising role one message from the human, and its request is the answer (218.710281ms)
|
||||||
|
TypeError: Cannot read properties of undefined (reading 'business')
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:66:18)
|
||||||
|
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)
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:84:1
|
||||||
|
✖ a cross-role decision resolved by its arbiter comes from that role and run, with no request (225.174299ms)
|
||||||
|
TypeError: Cannot read properties of undefined (reading 'from_role')
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:89:18)
|
||||||
|
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)
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:100:1
|
||||||
|
✖ a human withdraw sends one message with the note (226.660927ms)
|
||||||
|
TypeError: Cannot read properties of undefined (reading 'from_role')
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:105:18)
|
||||||
|
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||||
|
at Test.run (node:internal/test_runner/test:1402:25)
|
||||||
|
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||||
|
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||||
|
at Test.run (node:internal/test_runner/test:1467:12)
|
||||||
|
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:112:1
|
||||||
|
✖ a human seen before the close leaves the request on the close's answer, not the seen (259.745164ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||||
|
+ actual - expected
|
||||||
|
|
||||||
|
+ []
|
||||||
|
- [
|
||||||
|
- [
|
||||||
|
- '20c39b1f-3306-40df-a471-aa6271e18ab2',
|
||||||
|
- '4b498626-49c4-4661-80a6-d2248f380728'
|
||||||
|
- ],
|
||||||
|
- [
|
||||||
|
- 'a2027463-65f3-4d00-adaa-d366862871db',
|
||||||
|
- '88b76bbf-7265-4072-ae68-1e7efd7db688'
|
||||||
|
- ]
|
||||||
|
- ]
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:124: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: [ [ '20c39b1f-3306-40df-a471-aa6271e18ab2', '4b498626-49c4-4661-80a6-d2248f380728' ], [ 'a2027463-65f3-4d00-adaa-d366862871db', '88b76bbf-7265-4072-ae68-1e7efd7db688' ] ],
|
||||||
|
operator: 'deepStrictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:150:1
|
||||||
|
✖ refused closes send nothing: invalid-choice, decision-closed, not-resolver (258.390606ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||||
|
|
||||||
|
0 !== 1
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:157:10)
|
||||||
|
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||||
|
at Test.run (node:internal/test_runner/test:1402:25)
|
||||||
|
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||||
|
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||||
|
at Test.run (node:internal/test_runner/test:1467:12)
|
||||||
|
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||||
|
generatedMessage: true,
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
actual: 0,
|
||||||
|
expected: 1,
|
||||||
|
operator: 'strictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:164:1
|
||||||
|
✖ a raising role nobody holds at the close gets the message on the next claim (221.960203ms)
|
||||||
|
TypeError: Cannot read properties of undefined (reading 'id')
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:169:40)
|
||||||
|
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||||
|
at Test.run (node:internal/test_runner/test:1402:25)
|
||||||
|
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||||
|
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||||
|
at Test.run (node:internal/test_runner/test:1467:12)
|
||||||
|
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:179:1
|
||||||
|
✖ a human DECISION send keeps its own instruction as the request, not a close's answer (257.647089ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||||
|
+ actual - expected
|
||||||
|
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'human',
|
||||||
|
- '71deab94-e7d6-4638-87bb-4958f7830c89'
|
||||||
|
- ],
|
||||||
|
- [
|
||||||
|
- 'human',
|
||||||
|
'774ad656-85b1-4bbb-a17e-dda604e0021a'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:184: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: [ [ 'human', '774ad656-85b1-4bbb-a17e-dda604e0021a' ] ],
|
||||||
|
expected: [ [ 'human', '71deab94-e7d6-4638-87bb-4958f7830c89' ], [ 'human', '774ad656-85b1-4bbb-a17e-dda604e0021a' ] ],
|
||||||
|
operator: 'deepStrictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
test at tests/single-use.test.mjs:206:1
|
||||||
|
✖ message.send consumes approval and prevents a later send or authorize (166.862165ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||||
|
|
||||||
|
0 !== 1
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/single-use.test.mjs:220:10)
|
||||||
|
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||||
|
at Test.run (node:internal/test_runner/test:1402:25)
|
||||||
|
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||||
|
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||||
|
at Test.run (node:internal/test_runner/test:1467:12)
|
||||||
|
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||||
|
generatedMessage: true,
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
actual: 0,
|
||||||
|
expected: 1,
|
||||||
|
operator: 'strictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
@@ -0,0 +1,341 @@
|
|||||||
|
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (127.051943ms)
|
||||||
|
✔ agents: working counts what the holder received and has not read; routed alone is not working (278.816578ms)
|
||||||
|
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (515.446658ms)
|
||||||
|
✔ agents: working wins over waiting, and Input needed still shows (182.006528ms)
|
||||||
|
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (241.278085ms)
|
||||||
|
✔ agents: state reads only its own business, even for a run name both use (274.186907ms)
|
||||||
|
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (312.267166ms)
|
||||||
|
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (241.117821ms)
|
||||||
|
✔ agents: stranded is oldest first and capped at 50 for a role (955.484629ms)
|
||||||
|
✔ agents: stranded is per role and per business (365.658337ms)
|
||||||
|
✔ agents: a message routed to a held role and not yet received is not stranded (141.238927ms)
|
||||||
|
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (170.294215ms)
|
||||||
|
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (381.381067ms)
|
||||||
|
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (401.201172ms)
|
||||||
|
✔ launch events require a human CLI capability; generic emit cannot forge authority events (186.780344ms)
|
||||||
|
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (223.574849ms)
|
||||||
|
✔ a recommendation that is not exactly one option key refuses invalid-recommendation after the options (413.975904ms)
|
||||||
|
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (200.452254ms)
|
||||||
|
✔ task action subjects and linked decision trail are complete and ordered (212.85943ms)
|
||||||
|
✔ launch binding is durable and reconnecting requires the identical trusted record (119.155544ms)
|
||||||
|
✔ business isolation includes inherited object names and cross-business message references (221.10867ms)
|
||||||
|
✔ authority never transfers between action, run, target, unresolved or replaced role holder (299.921272ms)
|
||||||
|
✔ task projection uses schema current view, skipping earlier and equal-start polls (171.252369ms)
|
||||||
|
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (229.582819ms)
|
||||||
|
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (150.670515ms)
|
||||||
|
✔ both arbiters require human resolution when their cross-role route is themselves (263.501938ms)
|
||||||
|
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (150.319315ms)
|
||||||
|
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.074842ms)
|
||||||
|
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (115.606729ms)
|
||||||
|
✔ credentials refuses a role session and any argument (240.018152ms)
|
||||||
|
✔ credentials reads the status on each request, and a broker without one lists none (328.518677ms)
|
||||||
|
✔ the broker process answers credentials without a token, path or variable in its output, log or store (259.794185ms)
|
||||||
|
✔ only validated broker references load; returned data and exceptions cannot expose a known token (3.756017ms)
|
||||||
|
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (4.978146ms)
|
||||||
|
✔ expiry refuses use and env references never become client data (0.884747ms)
|
||||||
|
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.332569ms)
|
||||||
|
✔ opaque tokens shorter than 16 characters refuse before use (0.34541ms)
|
||||||
|
✖ a human resolve sends the raising role one message from the human, and its request is the answer (238.003301ms)
|
||||||
|
✖ a cross-role decision resolved by its arbiter comes from that role and run, with no request (321.036389ms)
|
||||||
|
✖ a human withdraw sends one message with the note (355.779839ms)
|
||||||
|
✖ a human seen before the close leaves the request on the close's answer, not the seen (318.362764ms)
|
||||||
|
✔ the raising run's withdraw, a supersede, a within-role raise and seen send nothing (372.035613ms)
|
||||||
|
✖ refused closes send nothing: invalid-choice, decision-closed, not-resolver (399.523409ms)
|
||||||
|
✖ a raising role nobody holds at the close gets the message on the next claim (280.815533ms)
|
||||||
|
✖ a human DECISION send keeps its own instruction as the request, not a close's answer (294.358773ms)
|
||||||
|
✔ roles lists open decisions routed to a role, and never the human inbox (245.230106ms)
|
||||||
|
✔ recent lists closed decisions of every route, newest close first, with how each closed (425.064796ms)
|
||||||
|
✔ recent gives the closing event as closed when the decision was seen first (398.286639ms)
|
||||||
|
✔ recent stops at RECENT rows (1047.720221ms)
|
||||||
|
✔ every row is what trail already gives the same session for that id (272.504631ms)
|
||||||
|
✔ a role session can't list decisions, so it can't read another role's open decisions (433.302722ms)
|
||||||
|
✔ decisions is a read: one business, strict arguments, nothing written (414.546827ms)
|
||||||
|
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (128.910921ms)
|
||||||
|
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (197.807721ms)
|
||||||
|
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (216.041927ms)
|
||||||
|
✔ endLaunch leaves a claim another run took alone (251.831617ms)
|
||||||
|
✔ refuse records action.refused against the caller with the code only (200.181643ms)
|
||||||
|
✔ launches off refuses role.launch with launch-revoked until launches on (204.451987ms)
|
||||||
|
✔ broker process: launch ops authorize role.launch, record refusals and end runs (337.59656ms)
|
||||||
|
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (3.139607ms)
|
||||||
|
✔ process reader gets own kernel identity without exposing environment values (0.675947ms)
|
||||||
|
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.928795ms)
|
||||||
|
✔ real pid 1 remains inspectable when its environment is protected (0.424262ms)
|
||||||
|
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (216.198751ms)
|
||||||
|
✔ missing and foreign-business citations refuse and roll back message and grant (282.722258ms)
|
||||||
|
✔ cross-role sends still need a matching resolved decision and consume it once (400.243481ms)
|
||||||
|
✔ messages pages all human messages newest first without writes; read state is history (3168.995046ms)
|
||||||
|
✔ messages refuses role callers, invalid paging and cursors outside this human feed (268.649436ms)
|
||||||
|
✔ views messages passes only its paging arguments through (0.263169ms)
|
||||||
|
✔ messages isolates businesses even with a valid foreign cursor (178.365167ms)
|
||||||
|
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (198.364883ms)
|
||||||
|
✔ startup token refusal returns safe code without value or partial listening broker (38.929015ms)
|
||||||
|
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (270.937054ms)
|
||||||
|
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (209.10396ms)
|
||||||
|
✔ trusted host registers later launches; socket clients never have a registration verb (266.912669ms)
|
||||||
|
✔ runtime excludes declared project roots even when host supplies no repoRoots (37.550716ms)
|
||||||
|
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (209.314957ms)
|
||||||
|
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (281.335105ms)
|
||||||
|
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (300.46828ms)
|
||||||
|
✔ a message with no reply gives an empty list (235.518288ms)
|
||||||
|
✔ replies come oldest first and stop at REPLIES (1031.769238ms)
|
||||||
|
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (331.975567ms)
|
||||||
|
✔ strict arguments; a role session gets human-required (425.231526ms)
|
||||||
|
✔ views passes replies through as one call with the id (0.252551ms)
|
||||||
|
✔ requirements returns the committed list to a holding role session, and no one else (280.385026ms)
|
||||||
|
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (230.637085ms)
|
||||||
|
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (267.169551ms)
|
||||||
|
✔ a push is recorded as the action.allowed for git.push.working on the task, and only the host records one (288.320686ms)
|
||||||
|
✔ review.request names the latest pushed commit and sends the reviewer a REVIEW-REQUEST (376.042521ms)
|
||||||
|
✔ review.request says which run holds the reviewer role, or null, and a null request waits for a claim (388.985333ms)
|
||||||
|
✔ review.request refuses a commit that isn't the latest push, a non-reviewer and a role without the action (478.923208ms)
|
||||||
|
✔ reviewFetch answers only the addressed reviewer, for the request it cites (361.944517ms)
|
||||||
|
✔ review.verdict answers one request once, to the role that asked (328.389919ms)
|
||||||
|
✔ task.close on a pushed task needs an approve verdict for the latest push (438.782359ms)
|
||||||
|
✔ a refusal the host decided is filed on the task it names, and only a task ref counts (271.444343ms)
|
||||||
|
✔ v3b prototype refusals, views and append-only mutations (1393.607684ms)
|
||||||
|
✔ gated approval authorizes once, survives store reopen, and fresh approval works (341.674426ms)
|
||||||
|
✔ another run cannot consume an approval; a failed check leaves it usable (325.649012ms)
|
||||||
|
✔ two scheduled callers have exactly one grant and one consumed refusal (247.153169ms)
|
||||||
|
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (390.734888ms)
|
||||||
|
✔ class drift gated to cross-role refuses before consumption (309.953865ms)
|
||||||
|
✔ class drift cross-role to gated refuses before consumption (364.04848ms)
|
||||||
|
✔ class drift gated to within-role refuses before consumption (240.142958ms)
|
||||||
|
✔ class drift cross-role to within-role refuses before consumption (279.729629ms)
|
||||||
|
✔ class drift within-role to gated refuses before consumption (214.017655ms)
|
||||||
|
✔ class drift within-role to cross-role refuses before consumption (229.339743ms)
|
||||||
|
✔ message.send consumes approval and prevents a later send or authorize (239.033653ms)
|
||||||
|
✔ role.revoke consumes approval and prevents a later revoke or authorize (288.537161ms)
|
||||||
|
✔ a business that never switched launches reads allowed with no time and no event (135.454114ms)
|
||||||
|
✔ launches revoked and then restored read allowed, with the restore time and event id (233.335119ms)
|
||||||
|
✔ a task closed on the tracker leaves the open list, and total counts the open ones (248.038963ms)
|
||||||
|
✔ a tombstone is not open (234.269116ms)
|
||||||
|
✔ the open list stops at the cap and says so; total still counts every open task (156.586227ms)
|
||||||
|
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (185.386832ms)
|
||||||
|
✔ a human session reads the same state, with the same cap (166.956434ms)
|
||||||
|
✔ reading state writes nothing: every table keeps its row count (228.921497ms)
|
||||||
|
✔ creates private WAL store and excludes a second writer until explicit close (191.466756ms)
|
||||||
|
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (222.846636ms)
|
||||||
|
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (208.245211ms)
|
||||||
|
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (309.537185ms)
|
||||||
|
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (125.124844ms)
|
||||||
|
✔ async transactions refuse before invoking their function (105.448447ms)
|
||||||
|
✔ the session default fits under the reply cap and pages to the end with next (301.465567ms)
|
||||||
|
✔ the short form: no description, assignees by role, every listed key (298.897511ms)
|
||||||
|
✔ each filter, alone and together, and total counts every match (395.292256ms)
|
||||||
|
✔ task_ref reads one task in full, as the whole board has it (294.871573ms)
|
||||||
|
✔ no arguments is the read the Console and mosaic make, unchanged (287.582107ms)
|
||||||
|
✔ an unknown argument or a bad value refuses invalid-request (958.667843ms)
|
||||||
|
✔ tasks rows carry priority and requirement for every session that reads tasks (372.721082ms)
|
||||||
|
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (485.110285ms)
|
||||||
|
✔ an empty business and one with snapshots but no task.created read cleanly (143.365116ms)
|
||||||
|
✔ recordTask keeps sync reads and a role write apart (202.459755ms)
|
||||||
|
✔ read_at must be one canonical UTC format, so the projection compares strings safely (179.861186ms)
|
||||||
|
✔ a bad entry refuses the whole record (180.622011ms)
|
||||||
|
✔ taskView reads the projection for one business (159.272119ms)
|
||||||
|
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (259.700365ms)
|
||||||
|
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (421.064513ms)
|
||||||
|
✔ without an adapter the server refuses every task verb (229.529995ms)
|
||||||
|
✔ the runtime refuses an invalid adapter and closes a valid one (243.60245ms)
|
||||||
|
✔ the process loads the S3 adapter from plain-data trackers (273.683646ms)
|
||||||
|
✔ the tester binds only when the business sets bus.tester to exactly true (1003.557936ms)
|
||||||
|
✔ a business may not name a role tester, so the tester address is never a role (262.683275ms)
|
||||||
|
✔ every verb outside the tester list refuses tester-refused and is recorded against the tester (590.868158ms)
|
||||||
|
✔ a tester message is from tester, starts [mos test], and writes no human.input or authority (250.01781ms)
|
||||||
|
✔ a role answers a tester only in reply to a tester message, and the answer never reaches the human (236.906954ms)
|
||||||
|
✔ the tester reads the human views a reader reads (87.225148ms)
|
||||||
|
✔ tester proof binds its own CLI, start time and nonce, refuses a managed run and allows an agent (1.074013ms)
|
||||||
|
✔ neither CLI's nonce proves the other: each proof is bound to its own CLI and nonce variable (0.35063ms)
|
||||||
|
✔ testerNonce reads only the tester's variable and gives null on any read error (0.189331ms)
|
||||||
|
✔ an envelope carries exactly one of cap, human and tester; no tester authenticator refuses (58.04999ms)
|
||||||
|
✔ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (724.68049ms)
|
||||||
|
✔ socket capability stamps launch identity; shared views use wire, no SQL client (243.228244ms)
|
||||||
|
✔ two wire claims serialize; a lost reply never automatically retries (206.00947ms)
|
||||||
|
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (124.570924ms)
|
||||||
|
✔ client preserves UTF-8 when a response divides a multibyte character (12.173345ms)
|
||||||
|
✔ committed mutation followed by dropped reply reports unknown and is never retried (152.016474ms)
|
||||||
|
ℹ tests 156
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 149
|
||||||
|
ℹ fail 7
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 4770.648373
|
||||||
|
|
||||||
|
✖ failing tests:
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:59:1
|
||||||
|
✖ a human resolve sends the raising role one message from the human, and its request is the answer (238.003301ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||||
|
|
||||||
|
'human' !== 'coder'
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:69: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: 'human',
|
||||||
|
expected: 'coder',
|
||||||
|
operator: 'strictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:84:1
|
||||||
|
✖ a cross-role decision resolved by its arbiter comes from that role and run, with no request (321.036389ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||||
|
|
||||||
|
'cto' !== 'coder'
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:91: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: 'cto',
|
||||||
|
expected: 'coder',
|
||||||
|
operator: 'strictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:100:1
|
||||||
|
✖ a human withdraw sends one message with the note (355.779839ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||||
|
|
||||||
|
'pm' !== 'coder'
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:106: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: 'pm',
|
||||||
|
expected: 'coder',
|
||||||
|
operator: 'strictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:112:1
|
||||||
|
✖ a human seen before the close leaves the request on the close's answer, not the seen (318.362764ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||||
|
+ actual - expected
|
||||||
|
|
||||||
|
+ []
|
||||||
|
- [
|
||||||
|
- [
|
||||||
|
- 'b54b78c9-0c8e-49a1-842c-0e20f96635ce',
|
||||||
|
- '0f5e5e31-3132-4b0b-848d-9de2f4426bfb'
|
||||||
|
- ],
|
||||||
|
- [
|
||||||
|
- 'ba7ba65c-1bfe-4b86-9900-bf8fcb00b389',
|
||||||
|
- '1059c9a0-6985-4e5a-8b70-4f8c8c961a9c'
|
||||||
|
- ]
|
||||||
|
- ]
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:124: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: [ [ 'b54b78c9-0c8e-49a1-842c-0e20f96635ce', '0f5e5e31-3132-4b0b-848d-9de2f4426bfb' ], [ 'ba7ba65c-1bfe-4b86-9900-bf8fcb00b389', '1059c9a0-6985-4e5a-8b70-4f8c8c961a9c' ] ],
|
||||||
|
operator: 'deepStrictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:150:1
|
||||||
|
✖ refused closes send nothing: invalid-choice, decision-closed, not-resolver (399.523409ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||||
|
|
||||||
|
0 !== 1
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:161:10)
|
||||||
|
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||||
|
at Test.run (node:internal/test_runner/test:1402:25)
|
||||||
|
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||||
|
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||||
|
at Test.run (node:internal/test_runner/test:1467:12)
|
||||||
|
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||||
|
generatedMessage: true,
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
actual: 0,
|
||||||
|
expected: 1,
|
||||||
|
operator: 'strictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:164:1
|
||||||
|
✖ a raising role nobody holds at the close gets the message on the next claim (280.815533ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||||
|
+ actual - expected
|
||||||
|
|
||||||
|
{
|
||||||
|
+ address: null,
|
||||||
|
+ holder_run: null,
|
||||||
|
- address: 'coder-run-2',
|
||||||
|
- holder_run: 'coder-run-2',
|
||||||
|
op: 'routed',
|
||||||
|
+ transport: null
|
||||||
|
- transport: 'pi'
|
||||||
|
}
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:171: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: { op: 'routed', holder_run: null, transport: null, address: null },
|
||||||
|
expected: { op: 'routed', holder_run: 'coder-run-2', transport: 'pi', address: 'coder-run-2' },
|
||||||
|
operator: 'deepStrictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:179:1
|
||||||
|
✖ a human DECISION send keeps its own instruction as the request, not a close's answer (294.358773ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||||
|
+ actual - expected
|
||||||
|
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'human',
|
||||||
|
- 'aa056219-38ea-4eba-81c8-e006a301c843'
|
||||||
|
- ],
|
||||||
|
- [
|
||||||
|
- 'human',
|
||||||
|
'eaed5545-9a45-4304-9171-17b9b545d56a'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:184: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: [ [ 'human', 'eaed5545-9a45-4304-9171-17b9b545d56a' ] ],
|
||||||
|
expected: [ [ 'human', 'aa056219-38ea-4eba-81c8-e006a301c843' ], [ 'human', 'eaed5545-9a45-4304-9171-17b9b545d56a' ] ],
|
||||||
|
operator: 'deepStrictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (155.679427ms)
|
||||||
|
✔ agents: working counts what the holder received and has not read; routed alone is not working (228.816636ms)
|
||||||
|
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (376.479758ms)
|
||||||
|
✔ agents: working wins over waiting, and Input needed still shows (213.678122ms)
|
||||||
|
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (281.253863ms)
|
||||||
|
✔ agents: state reads only its own business, even for a run name both use (243.76749ms)
|
||||||
|
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (264.268777ms)
|
||||||
|
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (246.552807ms)
|
||||||
|
✔ agents: stranded is oldest first and capped at 50 for a role (985.265632ms)
|
||||||
|
✔ agents: stranded is per role and per business (353.598438ms)
|
||||||
|
✔ agents: a message routed to a held role and not yet received is not stranded (134.818228ms)
|
||||||
|
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (178.729898ms)
|
||||||
|
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (292.618924ms)
|
||||||
|
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (320.973371ms)
|
||||||
|
✔ launch events require a human CLI capability; generic emit cannot forge authority events (241.168275ms)
|
||||||
|
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (241.549275ms)
|
||||||
|
✔ a recommendation that is not exactly one option key refuses invalid-recommendation after the options (340.384759ms)
|
||||||
|
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (219.832761ms)
|
||||||
|
✔ task action subjects and linked decision trail are complete and ordered (220.170772ms)
|
||||||
|
✔ launch binding is durable and reconnecting requires the identical trusted record (146.069091ms)
|
||||||
|
✔ business isolation includes inherited object names and cross-business message references (248.317023ms)
|
||||||
|
✔ authority never transfers between action, run, target, unresolved or replaced role holder (310.642461ms)
|
||||||
|
✔ task projection uses schema current view, skipping earlier and equal-start polls (133.003874ms)
|
||||||
|
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (219.860305ms)
|
||||||
|
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (155.283459ms)
|
||||||
|
✔ both arbiters require human resolution when their cross-role route is themselves (234.622198ms)
|
||||||
|
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (139.621569ms)
|
||||||
|
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.029962ms)
|
||||||
|
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (115.699251ms)
|
||||||
|
✔ credentials refuses a role session and any argument (192.932ms)
|
||||||
|
✔ credentials reads the status on each request, and a broker without one lists none (273.789782ms)
|
||||||
|
✔ the broker process answers credentials without a token, path or variable in its output, log or store (210.221006ms)
|
||||||
|
✔ only validated broker references load; returned data and exceptions cannot expose a known token (3.43462ms)
|
||||||
|
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (3.204745ms)
|
||||||
|
✔ expiry refuses use and env references never become client data (0.797905ms)
|
||||||
|
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.693475ms)
|
||||||
|
✔ opaque tokens shorter than 16 characters refuse before use (0.378171ms)
|
||||||
|
✔ a human resolve sends the raising role one message from the human, and its request is the answer (257.071483ms)
|
||||||
|
✔ a cross-role decision resolved by its arbiter comes from that role and run, with no request (275.995648ms)
|
||||||
|
✔ a human withdraw sends one message with the note (297.584175ms)
|
||||||
|
✔ a human seen before the close leaves the request on the close's answer, not the seen (376.483247ms)
|
||||||
|
✖ the raising run's withdraw, a supersede, a within-role raise and seen send nothing (344.385709ms)
|
||||||
|
✔ refused closes send nothing: invalid-choice, decision-closed, not-resolver (347.396618ms)
|
||||||
|
✔ a raising role nobody holds at the close gets the message on the next claim (304.554923ms)
|
||||||
|
✔ a human DECISION send keeps its own instruction as the request, not a close's answer (306.483235ms)
|
||||||
|
✔ roles lists open decisions routed to a role, and never the human inbox (232.703993ms)
|
||||||
|
✔ recent lists closed decisions of every route, newest close first, with how each closed (332.850851ms)
|
||||||
|
✔ recent gives the closing event as closed when the decision was seen first (362.986666ms)
|
||||||
|
✔ recent stops at RECENT rows (1010.196527ms)
|
||||||
|
✔ every row is what trail already gives the same session for that id (290.725743ms)
|
||||||
|
✔ a role session can't list decisions, so it can't read another role's open decisions (490.709358ms)
|
||||||
|
✔ decisions is a read: one business, strict arguments, nothing written (397.878535ms)
|
||||||
|
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (168.765342ms)
|
||||||
|
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (143.633048ms)
|
||||||
|
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (182.158842ms)
|
||||||
|
✔ endLaunch leaves a claim another run took alone (202.461456ms)
|
||||||
|
✔ refuse records action.refused against the caller with the code only (172.515381ms)
|
||||||
|
✔ launches off refuses role.launch with launch-revoked until launches on (257.634182ms)
|
||||||
|
✔ broker process: launch ops authorize role.launch, record refusals and end runs (324.807898ms)
|
||||||
|
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.256252ms)
|
||||||
|
✔ process reader gets own kernel identity without exposing environment values (0.663726ms)
|
||||||
|
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.931853ms)
|
||||||
|
✔ real pid 1 remains inspectable when its environment is protected (0.346521ms)
|
||||||
|
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (225.507903ms)
|
||||||
|
✔ missing and foreign-business citations refuse and roll back message and grant (204.073493ms)
|
||||||
|
✔ cross-role sends still need a matching resolved decision and consume it once (287.68829ms)
|
||||||
|
✔ messages pages all human messages newest first without writes; read state is history (3026.245945ms)
|
||||||
|
✔ messages refuses role callers, invalid paging and cursors outside this human feed (272.958555ms)
|
||||||
|
✔ views messages passes only its paging arguments through (0.266002ms)
|
||||||
|
✔ messages isolates businesses even with a valid foreign cursor (170.7212ms)
|
||||||
|
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (205.584173ms)
|
||||||
|
✔ startup token refusal returns safe code without value or partial listening broker (36.267589ms)
|
||||||
|
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (203.536746ms)
|
||||||
|
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (189.087139ms)
|
||||||
|
✔ trusted host registers later launches; socket clients never have a registration verb (234.323285ms)
|
||||||
|
✔ runtime excludes declared project roots even when host supplies no repoRoots (44.270869ms)
|
||||||
|
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (235.118522ms)
|
||||||
|
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (266.695964ms)
|
||||||
|
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (237.421164ms)
|
||||||
|
✔ a message with no reply gives an empty list (194.97528ms)
|
||||||
|
✔ replies come oldest first and stop at REPLIES (967.674434ms)
|
||||||
|
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (360.496297ms)
|
||||||
|
✔ strict arguments; a role session gets human-required (432.303429ms)
|
||||||
|
✔ views passes replies through as one call with the id (0.269544ms)
|
||||||
|
✔ requirements returns the committed list to a holding role session, and no one else (242.283576ms)
|
||||||
|
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (176.513121ms)
|
||||||
|
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (210.497391ms)
|
||||||
|
✔ a push is recorded as the action.allowed for git.push.working on the task, and only the host records one (270.840796ms)
|
||||||
|
✔ review.request names the latest pushed commit and sends the reviewer a REVIEW-REQUEST (285.060433ms)
|
||||||
|
✔ review.request says which run holds the reviewer role, or null, and a null request waits for a claim (360.755143ms)
|
||||||
|
✔ review.request refuses a commit that isn't the latest push, a non-reviewer and a role without the action (507.272ms)
|
||||||
|
✔ reviewFetch answers only the addressed reviewer, for the request it cites (287.091317ms)
|
||||||
|
✔ review.verdict answers one request once, to the role that asked (363.303ms)
|
||||||
|
✔ task.close on a pushed task needs an approve verdict for the latest push (445.560687ms)
|
||||||
|
✔ a refusal the host decided is filed on the task it names, and only a task ref counts (284.893063ms)
|
||||||
|
✔ v3b prototype refusals, views and append-only mutations (1294.510682ms)
|
||||||
|
✔ gated approval authorizes once, survives store reopen, and fresh approval works (299.56167ms)
|
||||||
|
✔ another run cannot consume an approval; a failed check leaves it usable (269.273013ms)
|
||||||
|
✔ two scheduled callers have exactly one grant and one consumed refusal (211.031132ms)
|
||||||
|
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (450.006374ms)
|
||||||
|
✔ class drift gated to cross-role refuses before consumption (281.356936ms)
|
||||||
|
✔ class drift cross-role to gated refuses before consumption (325.79334ms)
|
||||||
|
✔ class drift gated to within-role refuses before consumption (259.02908ms)
|
||||||
|
✔ class drift cross-role to within-role refuses before consumption (305.720413ms)
|
||||||
|
✔ class drift within-role to gated refuses before consumption (241.327537ms)
|
||||||
|
✔ class drift within-role to cross-role refuses before consumption (210.43467ms)
|
||||||
|
✔ message.send consumes approval and prevents a later send or authorize (236.208269ms)
|
||||||
|
✔ role.revoke consumes approval and prevents a later revoke or authorize (276.783924ms)
|
||||||
|
✔ a business that never switched launches reads allowed with no time and no event (149.377236ms)
|
||||||
|
✔ launches revoked and then restored read allowed, with the restore time and event id (179.473999ms)
|
||||||
|
✔ a task closed on the tracker leaves the open list, and total counts the open ones (178.886528ms)
|
||||||
|
✔ a tombstone is not open (166.019868ms)
|
||||||
|
✔ the open list stops at the cap and says so; total still counts every open task (191.342678ms)
|
||||||
|
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (175.594744ms)
|
||||||
|
✔ a human session reads the same state, with the same cap (204.641799ms)
|
||||||
|
✔ reading state writes nothing: every table keeps its row count (185.186772ms)
|
||||||
|
✔ creates private WAL store and excludes a second writer until explicit close (188.019585ms)
|
||||||
|
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (249.205471ms)
|
||||||
|
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (242.199071ms)
|
||||||
|
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (203.879798ms)
|
||||||
|
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (129.020706ms)
|
||||||
|
✔ async transactions refuse before invoking their function (122.707227ms)
|
||||||
|
✔ the session default fits under the reply cap and pages to the end with next (373.888466ms)
|
||||||
|
✔ the short form: no description, assignees by role, every listed key (305.549396ms)
|
||||||
|
✔ each filter, alone and together, and total counts every match (360.085876ms)
|
||||||
|
✔ task_ref reads one task in full, as the whole board has it (338.123835ms)
|
||||||
|
✔ no arguments is the read the Console and mosaic make, unchanged (298.521178ms)
|
||||||
|
✔ an unknown argument or a bad value refuses invalid-request (914.248472ms)
|
||||||
|
✔ tasks rows carry priority and requirement for every session that reads tasks (402.016974ms)
|
||||||
|
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (413.181362ms)
|
||||||
|
✔ an empty business and one with snapshots but no task.created read cleanly (140.041462ms)
|
||||||
|
✔ recordTask keeps sync reads and a role write apart (228.522515ms)
|
||||||
|
✔ read_at must be one canonical UTC format, so the projection compares strings safely (118.213364ms)
|
||||||
|
✔ a bad entry refuses the whole record (139.029049ms)
|
||||||
|
✔ taskView reads the projection for one business (163.988964ms)
|
||||||
|
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (274.775615ms)
|
||||||
|
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (435.460265ms)
|
||||||
|
✔ without an adapter the server refuses every task verb (244.300939ms)
|
||||||
|
✔ the runtime refuses an invalid adapter and closes a valid one (230.634927ms)
|
||||||
|
✔ the process loads the S3 adapter from plain-data trackers (263.982795ms)
|
||||||
|
✔ the tester binds only when the business sets bus.tester to exactly true (976.666404ms)
|
||||||
|
✔ a business may not name a role tester, so the tester address is never a role (297.408483ms)
|
||||||
|
✔ every verb outside the tester list refuses tester-refused and is recorded against the tester (576.704025ms)
|
||||||
|
✔ a tester message is from tester, starts [mos test], and writes no human.input or authority (236.579286ms)
|
||||||
|
✔ a role answers a tester only in reply to a tester message, and the answer never reaches the human (199.71512ms)
|
||||||
|
✔ the tester reads the human views a reader reads (88.501719ms)
|
||||||
|
✔ tester proof binds its own CLI, start time and nonce, refuses a managed run and allows an agent (1.310335ms)
|
||||||
|
✔ neither CLI's nonce proves the other: each proof is bound to its own CLI and nonce variable (0.357844ms)
|
||||||
|
✔ testerNonce reads only the tester's variable and gives null on any read error (0.217464ms)
|
||||||
|
✔ an envelope carries exactly one of cap, human and tester; no tester authenticator refuses (58.776831ms)
|
||||||
|
✔ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (713.560499ms)
|
||||||
|
✔ socket capability stamps launch identity; shared views use wire, no SQL client (184.882055ms)
|
||||||
|
✔ two wire claims serialize; a lost reply never automatically retries (201.643474ms)
|
||||||
|
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (140.978829ms)
|
||||||
|
✔ client preserves UTF-8 when a response divides a multibyte character (12.410464ms)
|
||||||
|
✔ committed mutation followed by dropped reply reports unknown and is never retried (162.791971ms)
|
||||||
|
ℹ tests 156
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 155
|
||||||
|
ℹ fail 1
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 4609.88322
|
||||||
|
|
||||||
|
✖ failing tests:
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:135:1
|
||||||
|
✖ the raising run's withdraw, a supersede, a within-role raise and seen send nothing (344.385709ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||||
|
+ actual - expected
|
||||||
|
|
||||||
|
+ [
|
||||||
|
+ [Object: null prototype] {
|
||||||
|
+ at: '2026-10-14T16:54:11.095Z',
|
||||||
|
+ body: 'Decision a7bfc59f-e265-4450-bb06-ec13bd6cff45 on task.scope.change was withdrawn by coder-run.\n' +
|
||||||
|
+ 'Note: not needed',
|
||||||
|
+ business: 'demo',
|
||||||
|
+ class: 'DECISION',
|
||||||
|
+ corrects: null,
|
||||||
|
+ decision: 'a7bfc59f-e265-4450-bb06-ec13bd6cff45',
|
||||||
|
+ from_role: 'coder',
|
||||||
|
+ from_run: 'coder-run',
|
||||||
|
+ id: '31bec0a2-212b-40ca-8598-1fab4b6c81da',
|
||||||
|
+ in_reply_to: null,
|
||||||
|
+ seq: 1,
|
||||||
|
+ to_role: 'coder'
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
- []
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.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: [ { seq: 1, id: '31bec0a2-212b-40ca-8598-1fab4b6c81da', at: '2026-10-14T16:54:11.095Z', business: 'demo', from_role: 'coder', from_run: 'coder-run', to_role: 'coder', class: 'DECISION', in_reply_to: null, decision: 'a7bfc59f-e265-4450-bb06-ec13bd6cff45', corrects: null, body: 'Decision a7bfc59f-e265-4450-bb06-ec13bd6cff45 on task.scope.change was withdrawn by coder-run.\nNote: not needed' } ],
|
||||||
|
expected: [],
|
||||||
|
operator: 'deepStrictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (166.615884ms)
|
||||||
|
✔ agents: working counts what the holder received and has not read; routed alone is not working (262.731727ms)
|
||||||
|
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (398.115898ms)
|
||||||
|
✔ agents: working wins over waiting, and Input needed still shows (227.242125ms)
|
||||||
|
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (244.962314ms)
|
||||||
|
✔ agents: state reads only its own business, even for a run name both use (294.589746ms)
|
||||||
|
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (295.932139ms)
|
||||||
|
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (288.228369ms)
|
||||||
|
✔ agents: stranded is oldest first and capped at 50 for a role (980.542089ms)
|
||||||
|
✔ agents: stranded is per role and per business (383.348902ms)
|
||||||
|
✔ agents: a message routed to a held role and not yet received is not stranded (187.21319ms)
|
||||||
|
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (182.293229ms)
|
||||||
|
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (327.566536ms)
|
||||||
|
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (323.670908ms)
|
||||||
|
✔ launch events require a human CLI capability; generic emit cannot forge authority events (248.292065ms)
|
||||||
|
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (200.60848ms)
|
||||||
|
✔ a recommendation that is not exactly one option key refuses invalid-recommendation after the options (443.445252ms)
|
||||||
|
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (208.42066ms)
|
||||||
|
✔ task action subjects and linked decision trail are complete and ordered (268.203337ms)
|
||||||
|
✔ launch binding is durable and reconnecting requires the identical trusted record (141.839105ms)
|
||||||
|
✔ business isolation includes inherited object names and cross-business message references (251.536111ms)
|
||||||
|
✔ authority never transfers between action, run, target, unresolved or replaced role holder (315.947821ms)
|
||||||
|
✔ task projection uses schema current view, skipping earlier and equal-start polls (149.229849ms)
|
||||||
|
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (225.822492ms)
|
||||||
|
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (185.631627ms)
|
||||||
|
✔ both arbiters require human resolution when their cross-role route is themselves (279.573276ms)
|
||||||
|
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (148.657863ms)
|
||||||
|
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (3.127802ms)
|
||||||
|
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (109.637114ms)
|
||||||
|
✔ credentials refuses a role session and any argument (218.423931ms)
|
||||||
|
✔ credentials reads the status on each request, and a broker without one lists none (250.222036ms)
|
||||||
|
✔ the broker process answers credentials without a token, path or variable in its output, log or store (209.264268ms)
|
||||||
|
✔ only validated broker references load; returned data and exceptions cannot expose a known token (6.080339ms)
|
||||||
|
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (4.224581ms)
|
||||||
|
✔ expiry refuses use and env references never become client data (1.318125ms)
|
||||||
|
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (2.116535ms)
|
||||||
|
✔ opaque tokens shorter than 16 characters refuse before use (0.721808ms)
|
||||||
|
✔ a human resolve sends the raising role one message from the human, and its request is the answer (258.441604ms)
|
||||||
|
✔ a cross-role decision resolved by its arbiter comes from that role and run, with no request (300.816864ms)
|
||||||
|
✔ a human withdraw sends one message with the note (278.989584ms)
|
||||||
|
✖ a human seen before the close leaves the request on the close's answer, not the seen (373.888643ms)
|
||||||
|
✔ the raising run's withdraw, a supersede, a within-role raise and seen send nothing (414.620724ms)
|
||||||
|
✔ refused closes send nothing: invalid-choice, decision-closed, not-resolver (402.742287ms)
|
||||||
|
✔ a raising role nobody holds at the close gets the message on the next claim (354.104699ms)
|
||||||
|
✔ a human DECISION send keeps its own instruction as the request, not a close's answer (300.492072ms)
|
||||||
|
✔ roles lists open decisions routed to a role, and never the human inbox (236.30952ms)
|
||||||
|
✔ recent lists closed decisions of every route, newest close first, with how each closed (353.918592ms)
|
||||||
|
✔ recent gives the closing event as closed when the decision was seen first (342.223067ms)
|
||||||
|
✔ recent stops at RECENT rows (1120.875182ms)
|
||||||
|
✔ every row is what trail already gives the same session for that id (336.296651ms)
|
||||||
|
✔ a role session can't list decisions, so it can't read another role's open decisions (486.620023ms)
|
||||||
|
✔ decisions is a read: one business, strict arguments, nothing written (394.884928ms)
|
||||||
|
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (142.510049ms)
|
||||||
|
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (163.187761ms)
|
||||||
|
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (187.152939ms)
|
||||||
|
✔ endLaunch leaves a claim another run took alone (224.806527ms)
|
||||||
|
✔ refuse records action.refused against the caller with the code only (154.068178ms)
|
||||||
|
✔ launches off refuses role.launch with launch-revoked until launches on (259.251746ms)
|
||||||
|
✔ broker process: launch ops authorize role.launch, record refusals and end runs (299.662144ms)
|
||||||
|
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (3.506588ms)
|
||||||
|
✔ process reader gets own kernel identity without exposing environment values (0.694042ms)
|
||||||
|
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.059831ms)
|
||||||
|
✔ real pid 1 remains inspectable when its environment is protected (0.408648ms)
|
||||||
|
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (214.553125ms)
|
||||||
|
✔ missing and foreign-business citations refuse and roll back message and grant (229.896125ms)
|
||||||
|
✔ cross-role sends still need a matching resolved decision and consume it once (305.105975ms)
|
||||||
|
✔ messages pages all human messages newest first without writes; read state is history (3202.43496ms)
|
||||||
|
✔ messages refuses role callers, invalid paging and cursors outside this human feed (299.275548ms)
|
||||||
|
✔ views messages passes only its paging arguments through (0.335736ms)
|
||||||
|
✔ messages isolates businesses even with a valid foreign cursor (211.173263ms)
|
||||||
|
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (231.831117ms)
|
||||||
|
✔ startup token refusal returns safe code without value or partial listening broker (36.498399ms)
|
||||||
|
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (214.607498ms)
|
||||||
|
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (195.800901ms)
|
||||||
|
✔ trusted host registers later launches; socket clients never have a registration verb (211.312108ms)
|
||||||
|
✔ runtime excludes declared project roots even when host supplies no repoRoots (38.263466ms)
|
||||||
|
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (238.271582ms)
|
||||||
|
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (283.72485ms)
|
||||||
|
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (244.103115ms)
|
||||||
|
✔ a message with no reply gives an empty list (189.554947ms)
|
||||||
|
✔ replies come oldest first and stop at REPLIES (1066.3778ms)
|
||||||
|
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (399.088638ms)
|
||||||
|
✔ strict arguments; a role session gets human-required (454.893504ms)
|
||||||
|
✔ views passes replies through as one call with the id (0.274624ms)
|
||||||
|
✔ requirements returns the committed list to a holding role session, and no one else (251.935757ms)
|
||||||
|
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (195.553219ms)
|
||||||
|
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (217.670096ms)
|
||||||
|
✔ a push is recorded as the action.allowed for git.push.working on the task, and only the host records one (289.28833ms)
|
||||||
|
✔ review.request names the latest pushed commit and sends the reviewer a REVIEW-REQUEST (309.686883ms)
|
||||||
|
✔ review.request says which run holds the reviewer role, or null, and a null request waits for a claim (379.023572ms)
|
||||||
|
✔ review.request refuses a commit that isn't the latest push, a non-reviewer and a role without the action (538.02337ms)
|
||||||
|
✔ reviewFetch answers only the addressed reviewer, for the request it cites (338.847623ms)
|
||||||
|
✔ review.verdict answers one request once, to the role that asked (392.92198ms)
|
||||||
|
✔ task.close on a pushed task needs an approve verdict for the latest push (443.915986ms)
|
||||||
|
✔ a refusal the host decided is filed on the task it names, and only a task ref counts (293.47184ms)
|
||||||
|
✔ v3b prototype refusals, views and append-only mutations (1302.12853ms)
|
||||||
|
✔ gated approval authorizes once, survives store reopen, and fresh approval works (319.94535ms)
|
||||||
|
✔ another run cannot consume an approval; a failed check leaves it usable (278.607552ms)
|
||||||
|
✔ two scheduled callers have exactly one grant and one consumed refusal (199.124588ms)
|
||||||
|
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (422.084272ms)
|
||||||
|
✔ class drift gated to cross-role refuses before consumption (398.713723ms)
|
||||||
|
✔ class drift cross-role to gated refuses before consumption (324.766049ms)
|
||||||
|
✔ class drift gated to within-role refuses before consumption (279.884031ms)
|
||||||
|
✔ class drift cross-role to within-role refuses before consumption (311.347325ms)
|
||||||
|
✔ class drift within-role to gated refuses before consumption (244.897654ms)
|
||||||
|
✔ class drift within-role to cross-role refuses before consumption (226.050244ms)
|
||||||
|
✔ message.send consumes approval and prevents a later send or authorize (233.731616ms)
|
||||||
|
✔ role.revoke consumes approval and prevents a later revoke or authorize (330.359768ms)
|
||||||
|
✔ a business that never switched launches reads allowed with no time and no event (161.316004ms)
|
||||||
|
✔ launches revoked and then restored read allowed, with the restore time and event id (181.566157ms)
|
||||||
|
✔ a task closed on the tracker leaves the open list, and total counts the open ones (203.578627ms)
|
||||||
|
✔ a tombstone is not open (169.95717ms)
|
||||||
|
✔ the open list stops at the cap and says so; total still counts every open task (187.016734ms)
|
||||||
|
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (198.569214ms)
|
||||||
|
✔ a human session reads the same state, with the same cap (165.599577ms)
|
||||||
|
✔ reading state writes nothing: every table keeps its row count (262.57407ms)
|
||||||
|
✔ creates private WAL store and excludes a second writer until explicit close (171.737314ms)
|
||||||
|
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (228.76325ms)
|
||||||
|
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (226.015029ms)
|
||||||
|
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (305.126139ms)
|
||||||
|
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (132.824086ms)
|
||||||
|
✔ async transactions refuse before invoking their function (119.061228ms)
|
||||||
|
✔ the session default fits under the reply cap and pages to the end with next (392.349037ms)
|
||||||
|
✔ the short form: no description, assignees by role, every listed key (372.270024ms)
|
||||||
|
✔ each filter, alone and together, and total counts every match (409.061268ms)
|
||||||
|
✔ task_ref reads one task in full, as the whole board has it (372.123722ms)
|
||||||
|
✔ no arguments is the read the Console and mosaic make, unchanged (307.342066ms)
|
||||||
|
✔ an unknown argument or a bad value refuses invalid-request (1089.072189ms)
|
||||||
|
✔ tasks rows carry priority and requirement for every session that reads tasks (405.088393ms)
|
||||||
|
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (520.998232ms)
|
||||||
|
✔ an empty business and one with snapshots but no task.created read cleanly (152.35364ms)
|
||||||
|
✔ recordTask keeps sync reads and a role write apart (206.902934ms)
|
||||||
|
✔ read_at must be one canonical UTC format, so the projection compares strings safely (246.198044ms)
|
||||||
|
✔ a bad entry refuses the whole record (150.195143ms)
|
||||||
|
✔ taskView reads the projection for one business (171.50043ms)
|
||||||
|
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (307.503243ms)
|
||||||
|
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (453.103465ms)
|
||||||
|
✔ without an adapter the server refuses every task verb (256.195215ms)
|
||||||
|
✔ the runtime refuses an invalid adapter and closes a valid one (216.166129ms)
|
||||||
|
✔ the process loads the S3 adapter from plain-data trackers (354.663941ms)
|
||||||
|
✔ the tester binds only when the business sets bus.tester to exactly true (1112.159642ms)
|
||||||
|
✔ a business may not name a role tester, so the tester address is never a role (284.443728ms)
|
||||||
|
✔ every verb outside the tester list refuses tester-refused and is recorded against the tester (626.602767ms)
|
||||||
|
✔ a tester message is from tester, starts [mos test], and writes no human.input or authority (258.8014ms)
|
||||||
|
✔ a role answers a tester only in reply to a tester message, and the answer never reaches the human (244.478496ms)
|
||||||
|
✔ the tester reads the human views a reader reads (92.759249ms)
|
||||||
|
✔ tester proof binds its own CLI, start time and nonce, refuses a managed run and allows an agent (1.423519ms)
|
||||||
|
✔ neither CLI's nonce proves the other: each proof is bound to its own CLI and nonce variable (0.456231ms)
|
||||||
|
✔ testerNonce reads only the tester's variable and gives null on any read error (0.285149ms)
|
||||||
|
✔ an envelope carries exactly one of cap, human and tester; no tester authenticator refuses (62.046444ms)
|
||||||
|
✔ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (910.075344ms)
|
||||||
|
✔ socket capability stamps launch identity; shared views use wire, no SQL client (321.758443ms)
|
||||||
|
✔ two wire claims serialize; a lost reply never automatically retries (224.068728ms)
|
||||||
|
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (168.852244ms)
|
||||||
|
✔ client preserves UTF-8 when a response divides a multibyte character (13.384641ms)
|
||||||
|
✔ committed mutation followed by dropped reply reports unknown and is never retried (168.138065ms)
|
||||||
|
ℹ tests 156
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 155
|
||||||
|
ℹ fail 1
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 5075.827662
|
||||||
|
|
||||||
|
✖ failing tests:
|
||||||
|
|
||||||
|
test at tests/decision-close.test.mjs:112:1
|
||||||
|
✖ a human seen before the close leaves the request on the close's answer, not the seen (373.888643ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||||
|
+ actual - expected
|
||||||
|
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'3988bb98-7765-4258-9bf6-093b81288e52',
|
||||||
|
+ '7f24a187-fbeb-4546-a4ef-365c92a1cb74'
|
||||||
|
- 'efec1917-fe16-4c46-97fa-d767f68b9df3'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'cbcf387d-227c-460c-947a-d9e06475bdcd',
|
||||||
|
+ '618dcc40-6d36-4da3-b592-c726d64079a3'
|
||||||
|
- '82ac927f-d673-430f-bdab-09cbdae7e6c8'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r115/wt/packages/bus/tests/decision-close.test.mjs:124: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: [ [ '3988bb98-7765-4258-9bf6-093b81288e52', '7f24a187-fbeb-4546-a4ef-365c92a1cb74' ], [ 'cbcf387d-227c-460c-947a-d9e06475bdcd', '618dcc40-6d36-4da3-b592-c726d64079a3' ] ],
|
||||||
|
expected: [ [ '3988bb98-7765-4258-9bf6-093b81288e52', 'efec1917-fe16-4c46-97fa-d767f68b9df3' ], [ 'cbcf387d-227c-460c-947a-d9e06475bdcd', '82ac927f-d673-430f-bdab-09cbdae7e6c8' ] ],
|
||||||
|
operator: 'deepStrictEqual',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user