researcher: row 142 round 1 packet, mosaic talk prints only the reply to its own message (#1636)
Candidate on 8e2c5c2d0: 4 files, tree 91503e62b8e0ce7b1895ffb7f3b1bbe534b52bcb.
6 of 6 mutants killed, 3 controls pass, cli suite and every scripts/test-*.sh green.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
# Row 142 (#1636): mosaic talk prints only the reply to its own message
|
||||
|
||||
Seat: Researcher. Reviewer: Filbert. Gate owner: Sage.
|
||||
Brief: `docs/plans/2026-10-15_q167-north-star-rows.md`, section "mosaic talk prints only the reply to its own message".
|
||||
|
||||
**Base: 8e2c5c2d0** (detached worktree `/mnt/storage/scratch/researcher/q142`). Canonical HEAD was 10d4b526a at packet time; the five commits after
|
||||
8e2c5c2d0 are docs and queue only (`git diff --name-only 8e2c5c2d0 HEAD -- packages scripts docs/TOOLS.md package.json` is empty).
|
||||
I first built on e37d6e583; HEAD had moved and `packages/cli/src/cli.mjs` changed (row 144), so I rebased my diff onto 8e2c5c2d0 before any mutant or gate run. It applied without conflict.
|
||||
`row142.patch` is a diff of my changes only: 4 files, +43 −24. It applies with `git apply --cached --check` on 8e2c5c2d0 and round-trips to the candidate tree `91503e62b8e0ce7b1895ffb7f3b1bbe534b52bcb`.
|
||||
`candidate-manifest.sha256` lists the 4 files. Nothing is pushed, and no live store, unit, credential or `~/.mosaic` file was touched. I hold no Gitea credential; the in-review move and the review request are Sage's.
|
||||
|
||||
## What the row does
|
||||
|
||||
`packages/cli/src/cli.mjs`, in the `talk` receive loop (one added line):
|
||||
|
||||
```js
|
||||
if (!follow && m.in_reply_to !== sent.id) continue;
|
||||
```
|
||||
|
||||
- Without `--follow`, `talk` prints and marks read only the message whose `in_reply_to` is the one it sent. Every other message is skipped before the print and before `message.read`.
|
||||
- `--follow` and `mosaic follow` (`receiveOnly`, which sets `follow`) keep today's behaviour: they print and read everything until Ctrl-C.
|
||||
- The timeout error now ends "…it may still come, and mosaic inbox shows it". It used to say "the next mosaic talk shows it", which is false now: a later `talk` skips a late reply to an earlier message.
|
||||
|
||||
### Consequence to know about (not a defect, the brief asks for it)
|
||||
|
||||
`message.receive` delivers each message once (`packages/bus/src/broker.mjs:1258`, it selects messages with no `delivered` or `read` row). A message `talk` skips is therefore **delivered but unread**:
|
||||
`mosaic inbox` lists it (its unread count is "no read row", broker.mjs:1509), but `mosaic follow` will not print it, because `follow` only receives undelivered messages.
|
||||
So the place a skipped message shows up is `mosaic inbox`, which is what the brief says ("leaves the rest unread for `mosaic inbox`"), and that is why the timeout message names `inbox` only and not `follow`.
|
||||
A late reply that arrives after `talk` has exited and before any other receive is still undelivered, so `follow` would print it too. I did not write a test that locks in the `follow` limitation. No bus change was made or needed.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `packages/cli/src/cli.mjs` | the filter line, the timeout message, the comment above `talk` |
|
||||
| `packages/cli/tests/verbs.test.mjs` | three tests rewritten (below) |
|
||||
| `docs/TOOLS.md` | the `talk` sentence in the host-commands paragraph (listed in the brief) |
|
||||
| `packages/cli/README.md` | the `talk` bullet. **Outside the brief's listed files**; added because it described the old behaviour ("prints every message that arrives and marks it read", "the next `mosaic talk` prints it") and would now be wrong. Sage ruled it stays in (sage-0388). |
|
||||
|
||||
### Tests (`packages/cli/tests/verbs.test.mjs`)
|
||||
|
||||
1. "talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread" (was "prints and reads everything…"). During the wait a cto INFO note and an unsolicited coder INFO note arrive before the coder's reply. Asserts: neither note is printed, the reply is printed, exactly one `message.read` call, and `mosaic inbox` lists both notes as "messages: 2 unread". It runs with `--wait 5` so a reply that is never printed fails an assertion instead of waiting out the default 900 s.
|
||||
2. The timeout test asserts the new message text.
|
||||
3. "a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it" (was "…still reaches the next talk"). The next `talk` does not print the late reply, makes no `message.read` call, and `mosaic inbox` lists the reply as unread. The Console reply-view assertion (`s.read("replies", {id})`) is kept.
|
||||
|
||||
`follow.test.mjs` is unchanged; its two cases ("talk --follow continues after reply…", "follow prints an unsolicited message after plain talk returned") guard `--follow` and `follow`. `tester.test.mjs` is unchanged and passes.
|
||||
|
||||
## Mutants
|
||||
|
||||
`mutants.sh <worktree> <outdir>`, started 2026-10-15T22:39:03Z, results in `mutants-run.txt`, one `.out`, `.rc` and `.patch` per mutant in `mutants/`.
|
||||
Three controls (verbs, follow and tester tests on the unmutated candidate) pass 6/6 each. **All 6 mutants fail their tests**, each by a named assertion. The worktree was restored after the run (`git status` shows the same 4 files).
|
||||
|
||||
| Mutant | What it breaks | Killed by |
|
||||
|---|---|---|
|
||||
| m1 | the filter line removed, so talk prints and reads everything again | verbs: the other-messages test, the late-reply test |
|
||||
| m2 | the filter also applies under `--follow` and `follow` | follow.test.mjs: both cases |
|
||||
| m3 | the filter inverted, so only the other messages print | verbs: the other-messages test, the late-reply test |
|
||||
| m4 | only the reply is printed but every message is marked read | verbs: the other-messages test, the late-reply test |
|
||||
| m5 | the timeout message goes back to "the next mosaic talk shows it" | verbs: the timeout test |
|
||||
| m6 | any reply (any non-empty `in_reply_to`) is printed, so a late reply to an earlier message shows | verbs: the late-reply test |
|
||||
|
||||
**Correction to my first mutant run.** The first run (22:31:46Z) killed m1, m2, m4, m5 and m6 by assertion, but m3 ended with exit 124: the 400 s timeout. The inverted filter never prints the reply, and the first test waited out `talk`'s default 900 s, so the test hung and did not fail an assertion. A timeout is weak evidence of a kill, so I gave test 1 `--wait 5`, confirmed the cli verbs test still passes (6/6), deleted that run's output, and reran all 9 runs. m3 now fails two tests by assertion. The table is from the second run.
|
||||
|
||||
## Gate
|
||||
|
||||
`suites.sh <worktree> <outdir>`, 2026-10-15T22:39:44Z to 22:43:18Z (`gate-started.txt`, `gate-finished.txt`, `suites-run.txt`, output per suite in `suites/`). All rc=0. It runs the cli suite and **every** `scripts/test-*.sh` the brief names (10 of them), plus the other node suites:
|
||||
|
||||
| Suite | Result |
|
||||
|---|---|
|
||||
| cli | 206/206 |
|
||||
| business | 101/101 |
|
||||
| bus | 214/214 |
|
||||
| plugins | 21/21 |
|
||||
| tasks | 97/97 |
|
||||
| webui | 113/113 |
|
||||
| harness | 90/90 |
|
||||
| seat | 135/135 |
|
||||
| scripts/test-auth.sh | 15 passed, 0 failed |
|
||||
| scripts/test-conductor.sh | 17 passed, 0 failed |
|
||||
| scripts/test-config.sh | 24 passed, 0 failed |
|
||||
| scripts/test-discord.sh | 78 passed, 0 failed |
|
||||
| scripts/test-extension-package.sh | 18 passed, 0 failed |
|
||||
| scripts/test-foundation.sh | 44 passed, 0 failed |
|
||||
| scripts/test-land-work.sh | 84 passed, 0 failed |
|
||||
| scripts/test-queue.sh | 28 passed, 0 failed |
|
||||
| scripts/test-release.sh | 14 passed, 0 failed |
|
||||
| scripts/test-task.sh | 98 passed, 0 failed |
|
||||
|
||||
No flake (#258, #261, #262, #263) recurred (no match in any suite output). The slot was Sage's "slot clear" (sage-0392, 22:31:24Z); a busy check by exit status, `/proc` cwd links only, read CLEAR at 22:39:03Z and again before the gate. Docker: the gate left one network, `q142_default`, which is mine; I removed it with `docker network rm q142_default` and the network list now equals the before-list. No container of mine is left.
|
||||
|
||||
## Follow-up, not in this row: stale "the next mosaic talk prints it" text
|
||||
|
||||
These say a reply still reaches the next `mosaic talk`. After this row that is false. They are outside the row's owned files; I did not touch them. To list on #1636:
|
||||
|
||||
- `packages/webui/src/public/bus.js:261` (a comment) and `:280` (the Console's send-section text: "…never marks one delivered or read, so the next mosaic talk in a terminal still prints each one."). `bus.js` is under `public/`, so it needs decision 123 later.
|
||||
- `packages/webui/tests/send-browser.test.mjs:105` asserts that exact send-section text.
|
||||
- `packages/bus/src/broker.mjs:1519`, a comment about the `replies` read.
|
||||
- `docs/plans/2026-10-11_console-reply-view.md:28`.
|
||||
|
||||
## Not covered
|
||||
|
||||
- A live `mosaic talk` against a running bus. The evidence is the in-process broker tests; the row goes live in the re-pin after the 3e window.
|
||||
- Row 128's named threads (out of scope per the brief) and the bus (unchanged).
|
||||
- Carried, unrelated to this row: the two row 132 non-blocking notes (`change_organization` "replaces" wording versus per-instance merge; a revision-only call refused by the writer, not the schema), for the next relevant row.
|
||||
@@ -0,0 +1,4 @@
|
||||
e3c2337ecb83fd9187ba7b3e6887a97bc59bfbf747c6abe1a78eb378669e847f docs/TOOLS.md
|
||||
666a8b14bacb3ab8e3dd56a4233b4d69ffb103d78797522e7bf2468bb658631f packages/cli/README.md
|
||||
6a70d46c6d928ecde28bf49b0839133d56456043da48d060a7323c12d969fb4c packages/cli/src/cli.mjs
|
||||
d40868d425d3cf79cd6cab0e98c1796365b398b93ef699d828977f0595576dcf packages/cli/tests/verbs.test.mjs
|
||||
@@ -0,0 +1,4 @@
|
||||
docs/TOOLS.md
|
||||
packages/cli/README.md
|
||||
packages/cli/src/cli.mjs
|
||||
packages/cli/tests/verbs.test.mjs
|
||||
@@ -0,0 +1 @@
|
||||
2026-10-15T22:43:18Z
|
||||
@@ -0,0 +1 @@
|
||||
2026-10-15T22:39:44Z
|
||||
@@ -0,0 +1 @@
|
||||
2026-10-15T22:39:28Z
|
||||
@@ -0,0 +1,26 @@
|
||||
control-verbs: exit 0; ℹ tests 6 ℹ pass 6 ℹ fail 0
|
||||
control-follow: exit 0; ℹ tests 6 ℹ pass 6 ℹ fail 0
|
||||
control-tester: exit 0; ℹ tests 6 ℹ pass 6 ℹ fail 0
|
||||
m1-filter-removed: exit 1; ℹ tests 6 ℹ pass 4 ℹ fail 2
|
||||
failed: ✖ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread
|
||||
m2-filter-under-follow: exit 1; ℹ tests 6 ℹ pass 4 ℹ fail 2
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ follow prints an unsolicited message after plain talk returned
|
||||
failed: ✖ talk --follow continues after reply and prints a later unsolicited message
|
||||
m3-filter-inverted: exit 1; ℹ tests 6 ℹ pass 4 ℹ fail 2
|
||||
failed: ✖ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread
|
||||
m4-read-all: exit 1; ℹ tests 6 ℹ pass 4 ℹ fail 2
|
||||
failed: ✖ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread
|
||||
m5-timeout-message: exit 1; ℹ tests 6 ℹ pass 5 ℹ fail 1
|
||||
failed: ✖ failing tests:
|
||||
failed: ✖ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show
|
||||
m6-any-reply: exit 1; ℹ tests 6 ℹ pass 5 ℹ fail 1
|
||||
failed: ✖ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it
|
||||
failed: ✖ failing tests:
|
||||
mutants finished
|
||||
@@ -0,0 +1 @@
|
||||
2026-10-15T22:39:03Z
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 142 (#1636): mosaic talk prints only the reply to its own message.
|
||||
# Each check must fail on a mutant. For each mutant this runs one named test file against the
|
||||
# real source (control) and against the source with one exact-once text edit, in the given worktree,
|
||||
# one test file at a time, and restores every source file on exit.
|
||||
# Usage: mutants.sh <worktree> <outdir> (MUTANT_DRY=1 only proves every edit matches exactly once)
|
||||
set -u
|
||||
WT=$(cd "$1" && pwd); OUT=$(mkdir -p "$2" && cd "$2" && pwd)
|
||||
cd "$WT" || exit 2
|
||||
BAK=$(mktemp -d "$OUT/.bak.XXXXXX")
|
||||
declare -A SAVED
|
||||
save() { [ -n "${SAVED[$1]:-}" ] || { mkdir -p "$BAK/$(dirname "$1")"; cp "$1" "$BAK/$1"; SAVED[$1]=1; }; }
|
||||
restore() { for f in "${!SAVED[@]}"; do cp "$BAK/$f" "$f"; done; rm -rf "$BAK"; }
|
||||
trap restore EXIT
|
||||
edit() { # file old new: exact-once replace
|
||||
save "$1"
|
||||
node -e '
|
||||
const fs = require("fs"); const [f, a, b] = process.argv.slice(1);
|
||||
const s = fs.readFileSync(f, "utf8"); const n = s.split(a).length - 1;
|
||||
if (n !== 1) { console.error("expected one match, found " + n); process.exit(3); }
|
||||
fs.writeFileSync(f, s.replace(a, () => b));
|
||||
' "$1" "$2" "$3"
|
||||
}
|
||||
run() { # name testfile
|
||||
if [ -n "${MUTANT_DRY:-}" ]; then echo "$1: edit ok (dry)"; return; fi
|
||||
setsid -w timeout -k 10 400 node --test "$2" > "$OUT/$1.out" 2>&1; echo "$?" > "$OUT/$1.rc"
|
||||
echo "$1: exit $(cat "$OUT/$1.rc"); $(grep -E '^ℹ (tests|pass|fail)' "$OUT/$1.out" | tr '\n' ' ')"
|
||||
grep -E '^✖' "$OUT/$1.out" | sort -u | sed 's/ ([0-9.]*ms)$//' | sed "s/^/ failed: /"
|
||||
}
|
||||
mutant() { # name testfile file old new
|
||||
edit "$3" "$4" "$5" || { echo "$1: edit failed"; return; }
|
||||
diff -u "$BAK/$3" "$3" > "$OUT/$1.patch"
|
||||
run "$1" "$2"
|
||||
cp "$BAK/$3" "$3"
|
||||
}
|
||||
C=packages/cli/src/cli.mjs; VT=packages/cli/tests/verbs.test.mjs; FT=packages/cli/tests/follow.test.mjs; TT=packages/cli/tests/tester.test.mjs
|
||||
for t in "$VT" "$FT" "$TT"; do run "control-$(basename "$t" .test.mjs)" "$t"; done
|
||||
|
||||
# m1: no filter, so talk prints and reads everything again
|
||||
mutant m1-filter-removed "$VT" "$C" ' if (!follow && m.in_reply_to !== sent.id) continue;
|
||||
' ''
|
||||
# m2: the filter also applies under --follow and follow, which must keep receiving everything
|
||||
mutant m2-filter-under-follow "$FT" "$C" 'if (!follow && m.in_reply_to !== sent.id) continue;' 'if (m.in_reply_to !== sent?.id) continue;'
|
||||
# m3: the filter is inverted, so only the other messages are printed
|
||||
mutant m3-filter-inverted "$VT" "$C" 'if (!follow && m.in_reply_to !== sent.id) continue;' 'if (!follow && m.in_reply_to === sent.id) continue;'
|
||||
# m4: only the reply is printed, but every message is marked read
|
||||
mutant m4-read-all "$VT" "$C" ' if (!follow && m.in_reply_to !== sent.id) continue;
|
||||
io.stdout.write(formatMessage(m));' ' if (follow || m.in_reply_to === sent.id) io.stdout.write(formatMessage(m));'
|
||||
# m5: the timeout message goes back to claiming the next talk shows a late reply
|
||||
mutant m5-timeout-message "$VT" "$C" 'it may still come, and mosaic inbox shows it' 'it may still come, and the next mosaic talk shows it'
|
||||
# m6: any reply is printed, not just the reply to this message, so a late reply to an earlier one shows up
|
||||
mutant m6-any-reply "$VT" "$C" 'if (!follow && m.in_reply_to !== sent.id) continue;' 'if (!follow && !m.in_reply_to) continue;'
|
||||
echo "mutants finished"
|
||||
@@ -0,0 +1,14 @@
|
||||
✔ follow prints an unsolicited message after plain talk returned (27.51161ms)
|
||||
✔ talk --follow continues after reply and prints a later unsolicited message (28.563469ms)
|
||||
✔ inbox lists all unread human messages under decisions, does not deliver or read them (258.286737ms)
|
||||
✔ follow refuses agent context and bad arguments (9.212773ms)
|
||||
✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (8.646912ms)
|
||||
✔ plain talk leaves the process SIGINT handlers unchanged (11.27039ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 447.759697
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,14 @@
|
||||
✔ talk --tester sends a [mos test] message as tester from inside an agent and prints the role's reply (18.526245ms)
|
||||
✔ talk --tester refuses text without the [mos test] prefix before any call (9.414607ms)
|
||||
✔ the read verbs take --tester inside an agent, and the trail shows the tester as the sender (15.258247ms)
|
||||
✔ decide, stop and launches take no --tester (9.920099ms)
|
||||
✔ the tester transport runs tester-cli.mjs, and the tester's refusals exit 3 (0.342253ms)
|
||||
✔ with no stand-in transport, --tester runs tester-cli.mjs on the data root's socket (0.487351ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 152.093411
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,14 @@
|
||||
✔ talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread (44.703802ms)
|
||||
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1021.131725ms)
|
||||
✔ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it (2037.463685ms)
|
||||
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (12.645827ms)
|
||||
✔ launches off and on go to the broker and change the business's launch state (12.843876ms)
|
||||
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (12.265482ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 3234.274001
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,44 @@
|
||||
✖ talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread (49.165928ms)
|
||||
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1016.4752ms)
|
||||
✖ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it (2028.653238ms)
|
||||
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (11.883778ms)
|
||||
✔ launches off and on go to the broker and change the business's launch state (9.795634ms)
|
||||
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (9.620064ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 3228.498076
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/verbs.test.mjs:32:1
|
||||
✖ talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread (49.165928ms)
|
||||
AssertionError [ERR_ASSERTION]: another role's note and the same role's unsolicited note are not printed
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q142/packages/cli/tests/verbs.test.mjs:59:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'sent e9bdc2b7 to coder\ncto (INFO) 9407618a:\nfyi\ncoder (INFO) f0bdf77f:\nside note\ncoder (RESULT, re e9bdc2b7) 67da0a3f:\ndone: 3 files\n',
|
||||
expected: /fyi|side note/,
|
||||
operator: 'doesNotMatch',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/verbs.test.mjs:80:1
|
||||
✖ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it (2028.653238ms)
|
||||
AssertionError [ERR_ASSERTION]: the late reply answers the first message, not this one
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q142/packages/cli/tests/verbs.test.mjs:90:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'sent d54d6df6 to coder\ncoder (RESULT, re 115067ff) 117fe05d:\nlate answer\n',
|
||||
expected: /late answer/,
|
||||
operator: 'doesNotMatch',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-142/mutants/.bak.JCMHo4/packages/cli/src/cli.mjs 2026-10-15 17:39:06.993302741 -0500
|
||||
+++ packages/cli/src/cli.mjs 2026-10-15 17:39:07.016541710 -0500
|
||||
@@ -245,7 +245,6 @@
|
||||
let answered = false;
|
||||
for (const m of await call("message.receive")) {
|
||||
// Row 142 (#1636): without --follow only the reply to the sent message is printed and marked read; the rest stays unread for mosaic inbox.
|
||||
- if (!follow && m.in_reply_to !== sent.id) continue;
|
||||
io.stdout.write(formatMessage(m));
|
||||
await call("message.read", { id: m.id });
|
||||
if (m.in_reply_to === sent?.id) answered = true;
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,50 @@
|
||||
✖ follow prints an unsolicited message after plain talk returned (25.645191ms)
|
||||
✖ talk --follow continues after reply and prints a later unsolicited message (24.828758ms)
|
||||
✔ inbox lists all unread human messages under decisions, does not deliver or read them (233.348946ms)
|
||||
✔ follow refuses agent context and bad arguments (9.526264ms)
|
||||
✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (8.16306ms)
|
||||
✔ plain talk leaves the process SIGINT handlers unchanged (10.887113ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 406.380907
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/follow.test.mjs:6:35
|
||||
✖ follow prints an unsolicited message after plain talk returned (25.645191ms)
|
||||
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /later unsolicited/. Input:
|
||||
|
||||
'sent 090591e5 to coder\ncoder (INFO, re 090591e5) 9b794190:\ndirect answer\n'
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q142/packages/cli/tests/follow.test.mjs:22:33)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'sent 090591e5 to coder\ncoder (INFO, re 090591e5) 9b794190:\ndirect answer\n',
|
||||
expected: /later unsolicited/,
|
||||
operator: 'match',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/follow.test.mjs:6:35
|
||||
✖ talk --follow continues after reply and prints a later unsolicited message (24.828758ms)
|
||||
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /later unsolicited/. Input:
|
||||
|
||||
'sent ad8cdea3 to coder\ncoder (INFO, re ad8cdea3) b392439b:\ndirect answer\n'
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q142/packages/cli/tests/follow.test.mjs:22:33)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'sent ad8cdea3 to coder\ncoder (INFO, re ad8cdea3) b392439b:\ndirect answer\n',
|
||||
expected: /later unsolicited/,
|
||||
operator: 'match',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-142/mutants/.bak.JCMHo4/packages/cli/src/cli.mjs 2026-10-15 17:39:06.993302741 -0500
|
||||
+++ packages/cli/src/cli.mjs 2026-10-15 17:39:10.300588415 -0500
|
||||
@@ -245,7 +245,7 @@
|
||||
let answered = false;
|
||||
for (const m of await call("message.receive")) {
|
||||
// Row 142 (#1636): without --follow only the reply to the sent message is printed and marked read; the rest stays unread for mosaic inbox.
|
||||
- if (!follow && m.in_reply_to !== sent.id) continue;
|
||||
+ if (m.in_reply_to !== sent?.id) continue;
|
||||
io.stdout.write(formatMessage(m));
|
||||
await call("message.read", { id: m.id });
|
||||
if (m.in_reply_to === sent?.id) answered = true;
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,47 @@
|
||||
✖ talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread (5018.828082ms)
|
||||
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1016.166446ms)
|
||||
✖ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it (2021.33965ms)
|
||||
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (12.269856ms)
|
||||
✔ launches off and on go to the broker and change the business's launch state (10.050259ms)
|
||||
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (9.110922ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 8176.871683
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/verbs.test.mjs:32:1
|
||||
✖ talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread (5018.828082ms)
|
||||
AssertionError [ERR_ASSERTION]: no reply from coder within 5 s; it may still come, and mosaic inbox shows it
|
||||
|
||||
1 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q142/packages/cli/tests/verbs.test.mjs:54:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 1,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/verbs.test.mjs:80:1
|
||||
✖ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it (2021.33965ms)
|
||||
AssertionError [ERR_ASSERTION]: the late reply answers the first message, not this one
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q142/packages/cli/tests/verbs.test.mjs:90:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'sent 7115be4f to coder\ncoder (RESULT, re e5d11932) 630c4d76:\nlate answer\n',
|
||||
expected: /late answer/,
|
||||
operator: 'doesNotMatch',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-142/mutants/.bak.JCMHo4/packages/cli/src/cli.mjs 2026-10-15 17:39:06.993302741 -0500
|
||||
+++ packages/cli/src/cli.mjs 2026-10-15 17:39:10.756594896 -0500
|
||||
@@ -245,7 +245,7 @@
|
||||
let answered = false;
|
||||
for (const m of await call("message.receive")) {
|
||||
// Row 142 (#1636): without --follow only the reply to the sent message is printed and marked read; the rest stays unread for mosaic inbox.
|
||||
- if (!follow && m.in_reply_to !== sent.id) continue;
|
||||
+ if (!follow && m.in_reply_to === sent.id) continue;
|
||||
io.stdout.write(formatMessage(m));
|
||||
await call("message.read", { id: m.id });
|
||||
if (m.in_reply_to === sent?.id) answered = true;
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,60 @@
|
||||
✖ talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread (47.022181ms)
|
||||
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1016.879025ms)
|
||||
✖ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it (2023.992926ms)
|
||||
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (11.228423ms)
|
||||
✔ launches off and on go to the broker and change the business's launch state (10.228802ms)
|
||||
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (8.88076ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 2
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 3204.428537
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/verbs.test.mjs:32:1
|
||||
✖ talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread (47.022181ms)
|
||||
AssertionError [ERR_ASSERTION]: only the reply was marked read
|
||||
|
||||
3 !== 1
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q142/packages/cli/tests/verbs.test.mjs:60:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 3,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/verbs.test.mjs:80:1
|
||||
✖ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it (2023.992926ms)
|
||||
AssertionError [ERR_ASSERTION]: nothing was marked read
|
||||
+ actual - expected
|
||||
|
||||
+ [
|
||||
+ {
|
||||
+ args: {
|
||||
+ id: '247c3590-e7f0-485d-92bb-ed1a586cb5ec'
|
||||
+ },
|
||||
+ business: 'demo',
|
||||
+ verb: 'message.read'
|
||||
+ }
|
||||
+ ]
|
||||
- []
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q142/packages/cli/tests/verbs.test.mjs:91:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: [ { business: 'demo', verb: 'message.read', args: [Object] } ],
|
||||
expected: [],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-142/mutants/.bak.JCMHo4/packages/cli/src/cli.mjs 2026-10-15 17:39:06.993302741 -0500
|
||||
+++ packages/cli/src/cli.mjs 2026-10-15 17:39:18.983711674 -0500
|
||||
@@ -245,8 +245,7 @@
|
||||
let answered = false;
|
||||
for (const m of await call("message.receive")) {
|
||||
// Row 142 (#1636): without --follow only the reply to the sent message is printed and marked read; the rest stays unread for mosaic inbox.
|
||||
- if (!follow && m.in_reply_to !== sent.id) continue;
|
||||
- io.stdout.write(formatMessage(m));
|
||||
+ if (follow || m.in_reply_to === sent.id) io.stdout.write(formatMessage(m));
|
||||
await call("message.read", { id: m.id });
|
||||
if (m.in_reply_to === sent?.id) answered = true;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,35 @@
|
||||
✔ talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread (46.613338ms)
|
||||
✖ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1017.225734ms)
|
||||
✔ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it (2038.731695ms)
|
||||
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (11.224164ms)
|
||||
✔ launches off and on go to the broker and change the business's launch state (9.557485ms)
|
||||
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (8.944655ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 3223.292427
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/verbs.test.mjs:69:1
|
||||
✖ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1017.225734ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
+ actual - expected
|
||||
|
||||
+ 'no reply from coder within 1 s; it may still come, and the next mosaic talk shows it'
|
||||
- 'no reply from coder within 1 s; it may still come, and mosaic inbox shows it'
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q142/packages/cli/tests/verbs.test.mjs:77:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'no reply from coder within 1 s; it may still come, and the next mosaic talk shows it',
|
||||
expected: 'no reply from coder within 1 s; it may still come, and mosaic inbox shows it',
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-142/mutants/.bak.JCMHo4/packages/cli/src/cli.mjs 2026-10-15 17:39:06.993302741 -0500
|
||||
+++ packages/cli/src/cli.mjs 2026-10-15 17:39:22.240757824 -0500
|
||||
@@ -251,7 +251,7 @@
|
||||
if (m.in_reply_to === sent?.id) answered = true;
|
||||
}
|
||||
if (!follow && answered) return;
|
||||
- if (!follow && Date.now() >= deadline) throw new CliError(`no reply from ${to} within ${wait} s; it may still come, and mosaic inbox shows it`, 1);
|
||||
+ if (!follow && Date.now() >= deadline) throw new CliError(`no reply from ${to} within ${wait} s; it may still come, and the next mosaic talk shows it`, 1);
|
||||
if (controller.signal.aborted) break;
|
||||
await new Promise(resolve => {
|
||||
const wake = () => { clearTimeout(timer); controller.signal.removeEventListener("abort", wake); resolve(); };
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,30 @@
|
||||
✔ talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread (45.180878ms)
|
||||
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1029.933461ms)
|
||||
✖ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it (2026.339005ms)
|
||||
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (11.156598ms)
|
||||
✔ launches off and on go to the broker and change the business's launch state (9.454881ms)
|
||||
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (8.640883ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 3220.611189
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/verbs.test.mjs:80:1
|
||||
✖ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it (2026.339005ms)
|
||||
AssertionError [ERR_ASSERTION]: the late reply answers the first message, not this one
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/researcher/q142/packages/cli/tests/verbs.test.mjs:90:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'sent 361a691b to coder\ncoder (RESULT, re f0ea8a21) f0b67a84:\nlate answer\n',
|
||||
expected: /late answer/,
|
||||
operator: 'doesNotMatch',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /mnt/storage/src/mosaic-stack/agents/researcher/work/queue-142/mutants/.bak.JCMHo4/packages/cli/src/cli.mjs 2026-10-15 17:39:06.993302741 -0500
|
||||
+++ packages/cli/src/cli.mjs 2026-10-15 17:39:25.517804210 -0500
|
||||
@@ -245,7 +245,7 @@
|
||||
let answered = false;
|
||||
for (const m of await call("message.receive")) {
|
||||
// Row 142 (#1636): without --follow only the reply to the sent message is printed and marked read; the rest stays unread for mosaic inbox.
|
||||
- if (!follow && m.in_reply_to !== sent.id) continue;
|
||||
+ if (!follow && !m.in_reply_to) continue;
|
||||
io.stdout.write(formatMessage(m));
|
||||
await call("message.read", { id: m.id });
|
||||
if (m.in_reply_to === sent?.id) answered = true;
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,54 @@
|
||||
0a4a6649758e809db55af2bcadc229b905b2a242175d92856e693a9c75344197 build.md
|
||||
1220d1341f35527ebd9c2b8cc86fa049617a19a751f6e72b87e5c483517348e7 candidate-manifest.sha256
|
||||
f2575be42ec9507bd02f1cf14e0f6437600bab330d61a709738b61d743d7f89b changed-paths.txt
|
||||
843b5c33d823819f6bf96f037c339e191655a69d9a8552fc36d56b2be4852c8c gate-finished.txt
|
||||
7edeaae59b41bb6c700d1509416461f8d50e79c68f028ac49002460c39374ae5 gate-started.txt
|
||||
f8b4475e7ae8471cf9cee59f3c831b9c4a975625a4ae7e73ca73952b4785a650 mutants/control-follow.out
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa mutants/control-follow.rc
|
||||
10406f1926edc70c10ada2f852109d4e02aadcb4a2056582d0c30ccba518fcd4 mutants/control-tester.out
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa mutants/control-tester.rc
|
||||
2ab5e0a694146df25b289f01c2d270f311831c62d38ac4ba93952a04ba9a1732 mutants/control-verbs.out
|
||||
9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa mutants/control-verbs.rc
|
||||
313a13ce9d08098575730eac1a740136284959502cbd8035e1e7b06a4b3a800e mutants-finished.txt
|
||||
230bd32105ab348c65e00b89bd6447a2c0692f96e5bcadc595b4888704aebf5a mutants/m1-filter-removed.out
|
||||
54abe7b713ecece9a54b3adad9a02fd431cd46dd9eb8c9cf847ab937cf2c4949 mutants/m1-filter-removed.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 mutants/m1-filter-removed.rc
|
||||
ea3902d04b8f2d6508b9af53d8860bc4d7a744166a0687232db37ba5ce010a56 mutants/m2-filter-under-follow.out
|
||||
62ca1cc0118f1806f1fa844d37701ffb67ab2f025eacbe30cab188f8d1bb7ce9 mutants/m2-filter-under-follow.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 mutants/m2-filter-under-follow.rc
|
||||
8161eb9788f808854a192dafca620b77e57f4ffde88a8465bebd73c626e27189 mutants/m3-filter-inverted.out
|
||||
d0b47761233e1354ec84f72ea2529609481662cededa4d117c54aaa2ee1088ef mutants/m3-filter-inverted.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 mutants/m3-filter-inverted.rc
|
||||
03f6b04beb333f8826f754a7970d70f4ace1f5da69edea49e7685e5f274ffaa2 mutants/m4-read-all.out
|
||||
3d4230625d22cd6073abd3264c284cc7f6ca96d09d4a271a948ed8316ac5d30e mutants/m4-read-all.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 mutants/m4-read-all.rc
|
||||
7f5f9256992a49aff95b074489744abe81e4d4b2f4c4721f2b40024064140f9e mutants/m5-timeout-message.out
|
||||
07e0c65a779c86489243d3eb5bc9acf1150a7cb1073e28ef512003c92529c452 mutants/m5-timeout-message.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 mutants/m5-timeout-message.rc
|
||||
61f498dfd603d9538017eeeba267fc431415ccc99c23a0df0cbdbc65135078bc mutants/m6-any-reply.out
|
||||
a04c626dbfdafa7288f96f7894fa4942b66e0ec389d4e5f7d4635c87b12bca9a mutants/m6-any-reply.patch
|
||||
4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 mutants/m6-any-reply.rc
|
||||
5be97a88610ff69247c28ff7042b60f6e13ac0c863233eacd9bed6b73a98cab7 mutants-run.txt
|
||||
cfdef4b238e1268e1448ad32938dcf2784f6c17337ad4ff9ab5d9f3876b0a714 mutants.sh
|
||||
59bd998a0081470383457b67c50406c846a84b2ca5a0cb59aa0b6306f12e7c82 mutants-started.txt
|
||||
aeb274fa8aaccc77e03b3cdb5c7a2b94a6f1d8062bd60cf17189ef74ff8c37aa row142.patch
|
||||
cd6e7af45f8c295ce45a310e382112c19b49caed8a37b762af3e9b8761709213 suites/business.txt
|
||||
7bc1abafe74cb9227aa974ace125efe25afef3712632dce56b4dba1d177543fb suites/bus.txt
|
||||
8d41217bbd393e0d4fe430b0134c048b9e8c79d0b75f522a3fdcd8429140ff22 suites/cli.txt
|
||||
bbbe4dd08a391ad88626ebb234764556442098ccf143766b5e886446590c4406 suites/harness.txt
|
||||
bcd27dd40eb39f64b52bacd25b5644697c035ed957aa024c40ac8b4eb98be53a suites/plugins.txt
|
||||
6ea7b99e0d1ad7655031f9890603886d885982c711c97dd7a26bfd9c8c536760 suites-run.txt
|
||||
bd7e2aa2f2f830e0932f68128f806630611cca7a508748839589d0a14c67b2a7 suites/seat.txt
|
||||
58bbaae1184355b75f5e6c028d6061834cef982c04b4d197a00028258e0d9480 suites.sh
|
||||
a64c373b68d1a78127b32662d0c9b71d6ea4093c49de2ebe0950d6a20b372114 suites/tasks.txt
|
||||
f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 suites/test-auth.txt
|
||||
e6e89b2481177d3997b9321e167c27d7d5499c9ac9250b2eb6804ab7a5d75e82 suites/test-conductor.txt
|
||||
52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 suites/test-config.txt
|
||||
87d2e24ce0be7586e31ea1f2c9ce5618b3127515b3c3cd3893f2a0ed899d5822 suites/test-discord.txt
|
||||
580596f9be5f46a344dcb7b32cb5c8808e735e864611a3e4248784ce04056ad0 suites/test-extension-package.txt
|
||||
83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f suites/test-foundation.txt
|
||||
8e2254aa3e1bb9b205ab72c68273d447c0328c9f156ccba2a46f3a0476468b58 suites/test-land-work.txt
|
||||
72b23773423f3fbb15090edb09c4986c21df93f4485c11f90284c634de5e73c5 suites/test-queue.txt
|
||||
fa832794ee35025fb4559d58d01a270f6bff2eb65e863e9075b797f47fa68545 suites/test-release.txt
|
||||
ccea37f4ffde1f160dcd1a1864988083a645d974e892065c956b338d0f8c853a suites/test-task.txt
|
||||
58609f32f67baf0c4201c3f19d87b2ef68391dccc106a6e92132d6a8cfdc8156 suites/webui.txt
|
||||
@@ -0,0 +1,158 @@
|
||||
diff --git a/docs/TOOLS.md b/docs/TOOLS.md
|
||||
index baa06fad3..a0424e30c 100644
|
||||
--- a/docs/TOOLS.md
|
||||
+++ b/docs/TOOLS.md
|
||||
@@ -262,9 +262,10 @@ unit's `MOSAIC_CLAUDE_BIN`, from `--claude PATH` or else `command -v claude`
|
||||
PID namespace with a generated bundle, and `--pm` launches the business's
|
||||
`launch.by` instance at start. A host that starts ends the launches a
|
||||
crashed host left as `host-lost` and refuses with exit 3 on a
|
||||
-`sessions.json` it can't read. `talk` sends a REQUEST and prints what
|
||||
-arrives until the reply (polls every 2 s, `--wait` default 900; `0` only
|
||||
-sends). `stop` sends SIGTERM to a run's runner after checking it in
|
||||
+`sessions.json` it can't read. `talk` sends a REQUEST and prints only the
|
||||
+reply to it, marking only that read (polls every 2 s, `--wait` default 900;
|
||||
+`0` only sends); other messages stay unread for `mosaic inbox`, and
|
||||
+`--follow` and `follow` print and read everything. `stop` sends SIGTERM to a run's runner after checking it in
|
||||
`sessions.json`. `launches off|on` revokes and restores `role.launch` for
|
||||
the business; running sessions keep running. `launches list` reads
|
||||
`sessions.json` and needs no broker. `talk`, `stop` and `launches off|on`
|
||||
diff --git a/packages/cli/README.md b/packages/cli/README.md
|
||||
index da6877607..53bb2ab92 100644
|
||||
--- a/packages/cli/README.md
|
||||
+++ b/packages/cli/README.md
|
||||
@@ -62,12 +62,13 @@ mosaic launches list [--json]
|
||||
trail ends with its `task_ref` and `follow with: mosaic trail <task>`. It
|
||||
does not pull in the task's rows.
|
||||
- `talk` sends `<text>` to the role as a REQUEST, then polls the human's
|
||||
- inbox every 2 s for up to `--wait` seconds (default 900). It prints every
|
||||
- message that arrives and marks it read, and stops at the reply to its
|
||||
- REQUEST. No reply in time exits 1; the reply may still come, and the next
|
||||
- `mosaic talk` prints it. `--wait 0` only sends. `talk --follow` keeps
|
||||
- printing and marking messages read until Ctrl-C, including messages with
|
||||
- no `in_reply_to`. `follow` runs that same loop without sending. `--wait`
|
||||
+ inbox every 2 s for up to `--wait` seconds (default 900). It prints the
|
||||
+ reply to its REQUEST, marks only that read, and stops. Any other message
|
||||
+ that arrives meanwhile is not printed and stays unread; `mosaic inbox`
|
||||
+ lists it. No reply in time exits 1; the reply may still come, and
|
||||
+ `mosaic inbox` shows it. `--wait 0` only sends. `talk --follow` keeps
|
||||
+ printing and marking every message read until Ctrl-C, including messages
|
||||
+ with no `in_reply_to`. `follow` runs that same loop without sending. `--wait`
|
||||
and `--follow` together refuse. These commands consume the same human
|
||||
inbox; run one receiving CLI at a time. Console only reads history.
|
||||
- `launches off` and `on` are the broker's `launch.revoke` and
|
||||
diff --git a/packages/cli/src/cli.mjs b/packages/cli/src/cli.mjs
|
||||
index fbca789a9..17956ff1a 100644
|
||||
--- a/packages/cli/src/cli.mjs
|
||||
+++ b/packages/cli/src/cli.mjs
|
||||
@@ -211,8 +211,9 @@ function formatMessage(m) {
|
||||
}
|
||||
|
||||
// `mosaic talk <role> <text>`: a REQUEST to the role, then the human's inbox
|
||||
-// until the reply arrives or --wait runs out. --follow keeps receiving after
|
||||
-// that reply; follow starts the same receive loop without sending.
|
||||
+// until the reply to it arrives or --wait runs out; other messages stay unread.
|
||||
+// --follow prints and reads every message and keeps receiving after that
|
||||
+// reply; follow starts the same receive loop without sending.
|
||||
async function talk(rest, io, deps, receiveOnly = false) {
|
||||
const parsed = parse(rest, { flags: ["--follow", "--tester"], values: ["--business", ...(receiveOnly ? [] : ["--wait"])] });
|
||||
if (parsed.positional.length !== (receiveOnly ? 0 : 2)) throw usage(receiveOnly ? "follow takes no arguments" : "talk takes a role and one quoted text");
|
||||
@@ -243,12 +244,14 @@ async function talk(rest, io, deps, receiveOnly = false) {
|
||||
while (!controller.signal.aborted) {
|
||||
let answered = false;
|
||||
for (const m of await call("message.receive")) {
|
||||
+ // Row 142 (#1636): without --follow only the reply to the sent message is printed and marked read; the rest stays unread for mosaic inbox.
|
||||
+ if (!follow && m.in_reply_to !== sent.id) continue;
|
||||
io.stdout.write(formatMessage(m));
|
||||
await call("message.read", { id: m.id });
|
||||
if (m.in_reply_to === sent?.id) answered = true;
|
||||
}
|
||||
if (!follow && answered) return;
|
||||
- if (!follow && Date.now() >= deadline) throw new CliError(`no reply from ${to} within ${wait} s; it may still come, and the next mosaic talk shows it`, 1);
|
||||
+ if (!follow && Date.now() >= deadline) throw new CliError(`no reply from ${to} within ${wait} s; it may still come, and mosaic inbox shows it`, 1);
|
||||
if (controller.signal.aborted) break;
|
||||
await new Promise(resolve => {
|
||||
const wake = () => { clearTimeout(timer); controller.signal.removeEventListener("abort", wake); resolve(); };
|
||||
diff --git a/packages/cli/tests/verbs.test.mjs b/packages/cli/tests/verbs.test.mjs
|
||||
index 6bdbc6793..b9842cdd7 100644
|
||||
--- a/packages/cli/tests/verbs.test.mjs
|
||||
+++ b/packages/cli/tests/verbs.test.mjs
|
||||
@@ -28,32 +28,42 @@ function setup(t, deps = {}) {
|
||||
|
||||
const call = (b, cap, verb, args = {}) => b.request(cap, { verb, args });
|
||||
|
||||
-test("talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply", async (t) => {
|
||||
+// Row 142 (#1636): without --follow, talk prints and reads the reply to its own message and nothing else.
|
||||
+test("talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread", async (t) => {
|
||||
const s = setup(t);
|
||||
const cto = s.b.bindLaunch({ business: "demo", role: "cto", run: "cto-run", harness: "pi" });
|
||||
call(s.b, cto, "role.claim");
|
||||
- // The coder answers on the second poll; a cto note arrives first and is printed too.
|
||||
+ // The coder answers on the second poll; a cto note and an unsolicited coder note arrive first.
|
||||
let polls = 0;
|
||||
+ let note;
|
||||
+ let unsolicited;
|
||||
const transport = (business) => {
|
||||
const inner = s.transport(business);
|
||||
return async (verb, args) => {
|
||||
if (verb === "message.receive" && ++polls === 2) {
|
||||
const [m] = call(s.b, s.coder, "message.receive");
|
||||
- call(s.b, cto, "message.send", { to: m.from_role, body: "fyi", class: "INFO" });
|
||||
+ note = call(s.b, cto, "message.send", { to: m.from_role, body: "fyi", class: "INFO" });
|
||||
+ unsolicited = call(s.b, s.coder, "message.send", { to: m.from_role, body: "side note", class: "INFO" });
|
||||
call(s.b, s.coder, "message.send", { to: m.from_role, body: "done: 3 files", class: "RESULT", in_reply_to: m.id });
|
||||
}
|
||||
return inner(verb, args);
|
||||
};
|
||||
};
|
||||
- const r = await s.run(["talk", "coder", "fix the build", "--business", "demo"], io(), { transport });
|
||||
+ // A short --wait makes a reply that is never printed fail here instead of waiting out the default 900 s.
|
||||
+ const r = await s.run(["talk", "coder", "fix the build", "--wait", "5", "--business", "demo"], io(), { transport });
|
||||
assert.equal(r.code, 0, r.error);
|
||||
const sent = s.calls.find((c) => c.verb === "message.send");
|
||||
assert.deepEqual(sent.args, { to: "coder", body: "fix the build", class: "REQUEST" });
|
||||
assert.match(r.io.out.text, /^sent [0-9a-f]{8} to coder\n/);
|
||||
- assert.match(r.io.out.text, /cto \(INFO\) [0-9a-f]{8}:\nfyi\n/);
|
||||
assert.match(r.io.out.text, /coder \(RESULT, re [0-9a-f]{8}\) [0-9a-f]{8}:\ndone: 3 files\n$/);
|
||||
- assert.equal(s.calls.filter((c) => c.verb === "message.read").length, 2);
|
||||
- assert.deepEqual(await s.transport("demo")("message.receive"), [], "both were marked read");
|
||||
+ assert.doesNotMatch(r.io.out.text, /fyi|side note/, "another role's note and the same role's unsolicited note are not printed");
|
||||
+ assert.equal(s.calls.filter((c) => c.verb === "message.read").length, 1, "only the reply was marked read");
|
||||
+ // receive delivers a message once, so what talk skipped is no longer receivable; mosaic inbox lists it as unread.
|
||||
+ const inbox = io();
|
||||
+ await main(["inbox", "--business", "demo"], inbox, { system: { dataRoot: s.dataRoot }, transport: s.transport });
|
||||
+ assert.match(inbox.out.text, /messages: 2 unread\n/, "the rest stays unread for mosaic inbox");
|
||||
+ assert.match(inbox.out.text, new RegExp(`cto \\(INFO\\) ${note.id.slice(0, 8)}:\nfyi\n`));
|
||||
+ assert.match(inbox.out.text, new RegExp(`coder \\(INFO\\) ${unsolicited.id.slice(0, 8)}:\nside note\n`));
|
||||
});
|
||||
|
||||
test("talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show", async (t) => {
|
||||
@@ -63,11 +73,11 @@ test("talk --wait 0 only sends; no reply within --wait exits 1 and says where it
|
||||
assert.deepEqual(s.calls.map((c) => c.verb), ["message.send"]);
|
||||
const slow = await s.run(["talk", "coder", "hello", "--wait", "1", "--business", "demo"]);
|
||||
assert.equal(slow.code, 1);
|
||||
- // Row 81 (#1575): only mosaic talk shows a late reply; mosaic inbox lists decisions, never messages.
|
||||
- assert.equal(slow.error, "no reply from coder within 1 s; it may still come, and the next mosaic talk shows it");
|
||||
+ // Row 142 (#1636): a late reply stays unread, so mosaic inbox lists it.
|
||||
+ assert.equal(slow.error, "no reply from coder within 1 s; it may still come, and mosaic inbox shows it");
|
||||
});
|
||||
|
||||
-test("a late reply the Console has listed still reaches the next talk", async (t) => {
|
||||
+test("a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it", async (t) => {
|
||||
const s = setup(t);
|
||||
assert.equal((await s.run(["talk", "coder", "hello", "--wait", "1", "--business", "demo"])).code, 1);
|
||||
const { id } = s.store.get("SELECT id FROM messages WHERE from_role='human' ORDER BY seq DESC LIMIT 1");
|
||||
@@ -77,8 +87,12 @@ test("a late reply the Console has listed still reaches the next talk", async (t
|
||||
assert.deepEqual(s.read("replies", { id }).map((m) => m.id), [answer.id]);
|
||||
const next = await s.run(["talk", "coder", "again", "--wait", "1", "--business", "demo"]);
|
||||
assert.equal(next.code, 1, "no answer to the second one");
|
||||
- assert.match(next.io.out.text, new RegExp(`coder \\(RESULT, re ${id.slice(0, 8)}\\) ${answer.id.slice(0, 8)}:\nlate answer\n`));
|
||||
- assert.deepEqual(s.calls.filter((c) => c.verb === "message.read").map((c) => c.args.id), [answer.id]);
|
||||
+ assert.doesNotMatch(next.io.out.text, /late answer/, "the late reply answers the first message, not this one");
|
||||
+ assert.deepEqual(s.calls.filter((c) => c.verb === "message.read"), [], "nothing was marked read");
|
||||
+ const inbox = io();
|
||||
+ await main(["inbox", "--business", "demo"], inbox, { system: { dataRoot: s.dataRoot }, transport: s.transport });
|
||||
+ assert.match(inbox.out.text, /messages: 1 unread\n/);
|
||||
+ assert.match(inbox.out.text, new RegExp(`coder \\(RESULT, re ${id.slice(0, 8)}\\) ${answer.id.slice(0, 8)}:\nlate answer\n`));
|
||||
});
|
||||
|
||||
test("talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors", async (t) => {
|
||||
@@ -0,0 +1,19 @@
|
||||
cli: rc=0 ℹ tests 206 ℹ pass 206 ℹ fail 0
|
||||
business: rc=0 ℹ tests 101 ℹ pass 101 ℹ fail 0
|
||||
bus: rc=0 ℹ tests 214 ℹ pass 214 ℹ fail 0
|
||||
plugins: rc=0 ℹ tests 21 ℹ pass 21 ℹ fail 0
|
||||
tasks: rc=0 ℹ tests 97 ℹ pass 97 ℹ fail 0
|
||||
webui: rc=0 ℹ tests 113 ℹ pass 113 ℹ fail 0
|
||||
harness: rc=0 ℹ tests 90 ℹ pass 90 ℹ fail 0
|
||||
seat: rc=0 ℹ tests 135 ℹ pass 135 ℹ fail 0
|
||||
test-auth: rc=0 selftest: 15 passed, 0 failed
|
||||
test-conductor: rc=0 selftest: 17 passed, 0 failed
|
||||
test-config: rc=0 selftest: 24 passed, 0 failed
|
||||
test-discord: rc=0 discord suite: 78 passed, 0 failed
|
||||
test-extension-package: rc=0 extension package selftest: 18 passed, 0 failed
|
||||
test-foundation: rc=0 selftest: 44 passed, 0 failed
|
||||
test-land-work: rc=0 land-work selftests: 84 passed, 0 failed
|
||||
test-queue: rc=0 queue suite: 28 passed, 0 failed
|
||||
test-release: rc=0 selftest: 14 passed, 0 failed
|
||||
test-task: rc=0 selftest: 98 passed, 0 failed
|
||||
done
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 142 (#1636): the gate the brief names (cli suite and every scripts/test-*.sh), plus the other node suites,
|
||||
# run in the given worktree, every output teed to <outdir>.
|
||||
# Usage: suites.sh <worktree> <outdir>
|
||||
set -u -o pipefail
|
||||
WT=$(cd "$1" && pwd); OUT=$(mkdir -p "$2" && cd "$2" && pwd)
|
||||
cd "$WT" || exit 2
|
||||
node_suite() { # name dir
|
||||
setsid -w timeout -k 10 900 node --test "$2"/tests/*.test.mjs 2>&1 | tee "$OUT/$1.txt" >/dev/null
|
||||
echo "$1: rc=${PIPESTATUS[0]} $(grep -E '^ℹ (tests|pass|fail) ' "$OUT/$1.txt" | tr '\n' ' ')"
|
||||
}
|
||||
for d in cli business bus plugins tasks webui harness seat; do node_suite $d packages/$d; done
|
||||
for s in scripts/test-*.sh; do
|
||||
n=$(basename "$s" .sh)
|
||||
setsid -w timeout -k 10 1800 bash "$s" 2>&1 | tee "$OUT/$n.txt" >/dev/null
|
||||
echo "$n: rc=${PIPESTATUS[0]} $(grep -iE 'passed|PASS|ok' "$OUT/$n.txt" | tail -1)"
|
||||
done
|
||||
echo done
|
||||
@@ -0,0 +1,222 @@
|
||||
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (30.086151ms)
|
||||
✔ agents: working counts what the holder received and has not read; routed alone is not working (29.841969ms)
|
||||
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (48.818546ms)
|
||||
✔ agents: working wins over waiting, and Input needed still shows (21.980754ms)
|
||||
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (28.619579ms)
|
||||
✔ agents: state reads only its own business, even for a run name both use (29.261575ms)
|
||||
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (30.171389ms)
|
||||
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (30.644622ms)
|
||||
✔ agents: stranded is oldest first and capped at 50 for a role (130.498535ms)
|
||||
✔ agents: stranded is per role and per business (46.505288ms)
|
||||
✔ agents: a message routed to a held role and not yet received is not stranded (22.792403ms)
|
||||
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (30.892533ms)
|
||||
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (37.048799ms)
|
||||
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (34.9186ms)
|
||||
✔ launch events require a human CLI capability; generic emit cannot forge authority events (23.65404ms)
|
||||
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (22.470809ms)
|
||||
✔ a recommendation that is not exactly one option key refuses invalid-recommendation after the options (42.684396ms)
|
||||
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (25.248018ms)
|
||||
✔ task action subjects and linked decision trail are complete and ordered (33.2353ms)
|
||||
✔ launch binding is durable and reconnecting requires the identical trusted record (16.392624ms)
|
||||
✔ business isolation includes inherited object names and cross-business message references (30.48537ms)
|
||||
✔ authority never transfers between action, run, target, unresolved or replaced role holder (35.638337ms)
|
||||
✔ task projection uses schema current view, skipping earlier and equal-start polls (19.819824ms)
|
||||
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (33.196436ms)
|
||||
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (18.840645ms)
|
||||
✔ both arbiters require human resolution when their cross-role route is themselves (38.31574ms)
|
||||
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (32.809441ms)
|
||||
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.328659ms)
|
||||
✔ REQ-COS-1, REQ-ROLE-2: the chief of staff ends a run with a reason and no decision, and the reason is logged (38.217588ms)
|
||||
✔ REQ-ROLE-2: a role without role.end still needs a resolved decision to revoke (42.712562ms)
|
||||
✔ REQ-ROLE-2: storage accepts a revoke with a reason or a decision and aborts one with neither (29.476107ms)
|
||||
✔ REQ-ROLE-2: the chief of staff may still cite a decision, and a decision alone is the old path (27.294067ms)
|
||||
✔ REQ-ROLE-2: a role without role.end still ends a run through a resolved decision, which is recorded and has no reason (41.158293ms)
|
||||
✔ REQ-ROLE-2: a reason is required, bounded, and never sent with a decision (37.127744ms)
|
||||
✔ REQ-COS-1: the chief of staff cannot end its own run or the assistant's through role.end (33.047411ms)
|
||||
✔ REQ-LIMIT-1, REQ-COS-1: organization.set from the chief of staff reaches the shared writer, named as the actor (32.794317ms)
|
||||
✔ REQ-LIMIT-1, REQ-COS-1: a refused organization.set logs action.refused and no action.allowed, like a refused limits.set (38.851681ms)
|
||||
✔ REQ-LIMIT-1: organization.set is refused for every caller whose role file lacks organization.change (37.751782ms)
|
||||
✔ REQ-LIMIT-1: the chief of staff is logged as allowed, and organization.set takes only the human's keys (48.845991ms)
|
||||
✔ REQ-ROLE-3, REQ-LIMIT-1: a chief-of-staff change is validated, written and logged like a human's; an invalid one is refused (96.781584ms)
|
||||
✔ REQ-ROLE-2, REQ-COS-1: the 3c→3d migration lets the chief of staff end a held role with a reason; neither is still refused (87.291229ms)
|
||||
✔ comment cursors isolate businesses, move only forward and remain append-only (22.817356ms)
|
||||
✔ only trusted sync writes validated cursors; events and cursor share rollback (16.202798ms)
|
||||
✔ explicit 3b copy migration preserves evidence and opens as 3e; normal startup never migrates (75.195859ms)
|
||||
✔ migration refuses active writers and drifted legacy schemas, with no partial upgrade (135.373853ms)
|
||||
✔ named migration registry chains 3b through 3e to a fixture-only future version in order (85.42894ms)
|
||||
✔ migration refuses mismatched source metadata without changing schema or evidence (62.003956ms)
|
||||
✔ offline command migrates only the supplied fixture root and reports usage errors (197.02806ms)
|
||||
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (22.50359ms)
|
||||
✔ credentials refuses a role session and any argument (22.934534ms)
|
||||
✔ credentials reads the status on each request, and a broker without one lists none (29.903826ms)
|
||||
✔ the broker process answers credentials without a token, path or variable in its output, log or store (101.87199ms)
|
||||
✔ only validated broker references load; returned data and exceptions cannot expose a known token (4.012601ms)
|
||||
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (3.015944ms)
|
||||
✔ expiry refuses use and env references never become client data (0.846228ms)
|
||||
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.799085ms)
|
||||
✔ opaque tokens shorter than 16 characters refuse before use (0.395589ms)
|
||||
✔ a human resolve sends the raising role one message from the human, and its request is the answer (50.30293ms)
|
||||
✔ a cross-role decision resolved by its arbiter comes from that role and run, with no request (29.445601ms)
|
||||
✔ a human withdraw sends one message with the note (35.626281ms)
|
||||
✔ a human seen before the close leaves the request on the close's answer, not the seen (39.52047ms)
|
||||
✔ the raising run's withdraw, a supersede, a within-role raise and seen send nothing (42.657832ms)
|
||||
✔ refused closes send nothing: invalid-choice, decision-closed, not-resolver (38.529363ms)
|
||||
✔ a raising role nobody holds at the close gets the message on the next claim (37.633627ms)
|
||||
✔ a human DECISION send keeps its own instruction as the request, not a close's answer (31.952873ms)
|
||||
✔ a cross-role raise whose arbiter the raiser does not hear from is refused and stores nothing (42.911644ms)
|
||||
✔ a gated raise by a role that does not hear from the human is refused and stores nothing (40.249067ms)
|
||||
✔ a route the raiser hears from is allowed, checked from the route to the raiser (44.515488ms)
|
||||
✔ a refused supersede withdraws nothing (38.777691ms)
|
||||
✔ the assistant's within-role and gated raises still work (41.244905ms)
|
||||
✔ roles lists open decisions routed to a role, and never the human inbox (38.275566ms)
|
||||
✔ recent lists closed decisions of every route, newest close first, with how each closed (41.645271ms)
|
||||
✔ recent gives the closing event as closed when the decision was seen first (40.242266ms)
|
||||
✔ recent stops at RECENT rows (132.625479ms)
|
||||
✔ every row is what trail already gives the same session for that id (53.296529ms)
|
||||
✔ a role session can't list decisions, so it can't read another role's open decisions (58.179814ms)
|
||||
✔ decisions is a read: one business, strict arguments, nothing written (54.844541ms)
|
||||
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (23.333944ms)
|
||||
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (17.909857ms)
|
||||
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (21.779362ms)
|
||||
✔ endLaunch leaves a claim another run took alone (22.771082ms)
|
||||
✔ refuse records action.refused against the caller with the code only (16.770681ms)
|
||||
✔ launches off refuses role.launch with launch-revoked until launches on (25.048362ms)
|
||||
✔ broker process: launch ops authorize role.launch, record refusals and end runs (115.671363ms)
|
||||
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.236046ms)
|
||||
✔ process reader gets own kernel identity without exposing environment values (0.773227ms)
|
||||
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.983196ms)
|
||||
✔ real pid 1 remains inspectable when its environment is protected (0.340869ms)
|
||||
✔ limits binds actor from human proof and rejects role, reader, tester and forged actor (32.461634ms)
|
||||
✔ runtime wires verified human settings to the actual shared file writer and actor receipt (58.159196ms)
|
||||
✔ uncertain organization receipts leave bus startup and reads alone but refuse writes until reconciled (56.600727ms)
|
||||
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (36.552712ms)
|
||||
✔ missing and foreign-business citations refuse and roll back message and grant (26.099759ms)
|
||||
✔ cross-role sends still need a matching resolved decision and consume it once (33.898515ms)
|
||||
✔ messages pages all human messages newest first without writes; read state is history (472.405714ms)
|
||||
✔ messages refuses role callers, invalid paging and cursors outside this human feed (34.516289ms)
|
||||
✔ views messages passes only its paging arguments through (0.315873ms)
|
||||
✔ messages isolates businesses even with a valid foreign cursor (24.217584ms)
|
||||
✔ the assistant sends to the user and to the chief of staff only (48.828918ms)
|
||||
✔ an assistant send to any other role, or to the tester, is refused and logged (51.669613ms)
|
||||
✔ only the user and the chief of staff may send to the assistant; the tester is refused by default (48.00653ms)
|
||||
✔ REQ-ASSIST-2 REQ-THREAD-1 thread.send to an instance that does not hear from the user is refused and stores nothing (39.595298ms)
|
||||
✔ a sender-side list alone refuses a send to a role that has no list (45.218485ms)
|
||||
✔ a target-side list alone refuses a send from a role that has no list (47.039466ms)
|
||||
✔ the sender list is checked before the target list (38.716645ms)
|
||||
✔ roles without the field behave exactly as before (62.871524ms)
|
||||
✔ a business with no role definitions at all is unlimited (44.381765ms)
|
||||
✔ a malformed messaging field refuses at boot (20.312469ms)
|
||||
✔ the assistant's review.request to a reviewer is refused and logged, even with an approved decision (48.295797ms)
|
||||
✔ a review.verdict to the assistant from a role outside its receiveFrom is refused and logged (49.08501ms)
|
||||
✔ review requests and verdicts between roles without the field are unchanged (33.689304ms)
|
||||
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (99.436564ms)
|
||||
✔ startup token refusal returns safe code without value or partial listening broker (68.947158ms)
|
||||
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (93.140071ms)
|
||||
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (152.736354ms)
|
||||
✔ trusted host registers later launches; socket clients never have a registration verb (99.217964ms)
|
||||
✔ runtime excludes declared project roots even when host supplies no repoRoots (58.246339ms)
|
||||
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (68.749048ms)
|
||||
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (47.048777ms)
|
||||
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (29.309345ms)
|
||||
✔ a message with no reply gives an empty list (24.405649ms)
|
||||
✔ replies come oldest first and stop at REPLIES (108.803235ms)
|
||||
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (41.73001ms)
|
||||
✔ strict arguments; a role session gets human-required (48.198613ms)
|
||||
✔ views passes replies through as one call with the id (0.375202ms)
|
||||
✔ requirements returns the committed list to a holding role session, and no one else (34.067309ms)
|
||||
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (21.395819ms)
|
||||
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (24.858272ms)
|
||||
✔ a push is recorded as the action.allowed for git.push.working on the task, and only the host records one (37.585071ms)
|
||||
✔ review.request names the latest pushed commit and sends the reviewer a REVIEW-REQUEST (33.417755ms)
|
||||
✔ review.request says which run holds the reviewer role, or null, and a null request waits for a claim (37.889026ms)
|
||||
✔ review.request refuses a commit that isn't the latest push, a non-reviewer and a role without the action (50.159979ms)
|
||||
✔ reviewFetch answers only the addressed reviewer, for the request it cites (33.74863ms)
|
||||
✔ review.verdict answers one request once, to the role that asked (51.596715ms)
|
||||
✔ task.close on a pushed task needs an approve verdict for the latest push (56.534148ms)
|
||||
✔ a refusal the host decided is filed on the task it names, and only a task ref counts (31.240407ms)
|
||||
✔ v3b prototype refusals, views and append-only mutations (126.357744ms)
|
||||
✔ gated approval authorizes once, survives store reopen, and fresh approval works (40.907093ms)
|
||||
✔ another run cannot consume an approval; a failed check leaves it usable (29.449316ms)
|
||||
✔ two scheduled callers have exactly one grant and one consumed refusal (23.0177ms)
|
||||
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (47.942894ms)
|
||||
✔ class drift gated to cross-role refuses before consumption (33.983488ms)
|
||||
✔ class drift cross-role to gated refuses before consumption (37.980696ms)
|
||||
✔ class drift gated to within-role refuses before consumption (36.515801ms)
|
||||
✔ class drift cross-role to within-role refuses before consumption (34.377646ms)
|
||||
✔ class drift within-role to gated refuses before consumption (28.277927ms)
|
||||
✔ class drift within-role to cross-role refuses before consumption (23.944677ms)
|
||||
✔ message.send consumes approval and prevents a later send or authorize (26.217872ms)
|
||||
✔ role.revoke consumes approval and prevents a later revoke or authorize (29.355731ms)
|
||||
✔ a business that never switched launches reads allowed with no time and no event (24.447844ms)
|
||||
✔ launches revoked and then restored read allowed, with the restore time and event id (23.181133ms)
|
||||
✔ a task closed on the tracker leaves the open list, and total counts the open ones (22.521648ms)
|
||||
✔ a tombstone is not open (20.046087ms)
|
||||
✔ the open list stops at the cap and says so; total still counts every open task (26.624368ms)
|
||||
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (23.921902ms)
|
||||
✔ a human session reads the same state, with the same cap (26.247291ms)
|
||||
✔ reading state writes nothing: every table keeps its row count (24.114797ms)
|
||||
✔ creates private WAL store and excludes a second writer until explicit close (19.745286ms)
|
||||
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (23.686085ms)
|
||||
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (24.365629ms)
|
||||
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (85.414964ms)
|
||||
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (16.97269ms)
|
||||
✔ async transactions refuse before invoking their function (12.523613ms)
|
||||
✔ the session default fits under the reply cap and pages to the end with next (200.435497ms)
|
||||
✔ the short form: no description, assignees by role, every listed key (93.010588ms)
|
||||
✔ each filter, alone and together, and total counts every match (164.360907ms)
|
||||
✔ task_ref reads one task in full, as the whole board has it (89.253095ms)
|
||||
✔ no arguments is the read the Console and mosaic make, unchanged (74.194184ms)
|
||||
✔ an unknown argument or a bad value refuses invalid-request (313.081912ms)
|
||||
✔ tasks rows carry priority and requirement for every session that reads tasks (63.719479ms)
|
||||
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (62.809157ms)
|
||||
✔ an empty business and one with snapshots but no task.created read cleanly (16.555671ms)
|
||||
✔ recordTask keeps sync reads and a role write apart (34.04358ms)
|
||||
✔ read_at must be one canonical UTC format, so the projection compares strings safely (16.275245ms)
|
||||
✔ a bad entry refuses the whole record (13.59456ms)
|
||||
✔ taskView reads the projection for one business (19.271537ms)
|
||||
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (30.049487ms)
|
||||
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (277.248392ms)
|
||||
✔ without an adapter the server refuses every task verb (17.877613ms)
|
||||
✔ the runtime refuses an invalid adapter and closes a valid one (21.53831ms)
|
||||
✔ the process loads the S3 adapter from plain-data trackers (116.327886ms)
|
||||
✔ the tester binds only when the business sets bus.tester to exactly true (105.155002ms)
|
||||
✔ a business may not name a role tester, so the tester address is never a role (28.103352ms)
|
||||
✔ every verb outside the tester list refuses tester-refused and is recorded against the tester (54.529141ms)
|
||||
✔ a tester message is from tester, starts [mos test], and writes no human.input or authority (24.108083ms)
|
||||
✔ a role answers a tester only in reply to a tester message, and the answer never reaches the human (27.216042ms)
|
||||
✔ the tester reads the human views a reader reads (24.987966ms)
|
||||
✔ tester proof binds its own CLI, start time and nonce, refuses a managed run and allows an agent (1.369905ms)
|
||||
✔ neither CLI's nonce proves the other: each proof is bound to its own CLI and nonce variable (0.39588ms)
|
||||
✔ testerNonce reads only the tester's variable and gives null on any read error (0.221503ms)
|
||||
✔ an envelope carries exactly one of cap, human and tester; no tester authenticator refuses (18.798099ms)
|
||||
✔ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (655.895935ms)
|
||||
✔ REQ-THREAD-1 REQ-ORG-2: the 3d→3e migration adds the thread tables and leaves messages from before threads as written (107.73796ms)
|
||||
✔ REQ-THREAD-1: a 3d bus with a changed schema is refused and keeps its version and tables (65.338173ms)
|
||||
✔ REQ-THREAD-1: the offline command prints the kept-message counts before the ready line (104.623804ms)
|
||||
✔ REQ-ROLE-2 REQ-THREAD-1: one holder per thread, through the broker and the trigger (48.264923ms)
|
||||
✔ REQ-ROLE-2 REQ-THREAD-1: the 3e triggers refuse writes that skip the broker (76.574417ms)
|
||||
✔ REQ-MSG-1: a message to or from the assistant names its thread, or is refused (34.614097ms)
|
||||
✔ REQ-MSG-1: the chief of staff's reply comes back to the thread that sent the work (521.541368ms)
|
||||
✔ REQ-MSG-1: a reply can't carry one thread instance's thread to another thread instance (16.76653ms)
|
||||
✔ REQ-THREAD-1 REQ-USER-2: one thread's messages never reach another thread's holder (21.286057ms)
|
||||
✔ REQ-LAUNCH-1: a message to a thread with no holder waits, and the next holder gets it (23.901975ms)
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1: threadIdle releases only a thread with nothing waiting, and only for its holder (27.11315ms)
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1: a message delivered to a run that ended unread doesn't hold up the next holder (23.335598ms)
|
||||
✔ REQ-ROLE-2 REQ-THREAD-1: an approved role.revoke never reaches a thread run, and the thread stays held (19.852163ms)
|
||||
✔ REQ-USER-2: the human verbs refuse another owner's thread (22.349179ms)
|
||||
✔ REQ-THREAD-1: thread.create refuses when the business has no thread instance or more than one (15.970591ms)
|
||||
✔ REQ-ORG-2: projects and threads are named once, and the repo names a business project (19.283703ms)
|
||||
✔ REQ-THREAD-2: thread.messages returns the thread's history in pages and writes nothing (70.32513ms)
|
||||
✔ socket capability stamps launch identity; shared views use wire, no SQL client (32.126839ms)
|
||||
✔ two wire claims serialize; a lost reply never automatically retries (27.026549ms)
|
||||
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (12.700315ms)
|
||||
✔ client preserves UTF-8 when a response divides a multibyte character (11.656101ms)
|
||||
✔ committed mutation followed by dropped reply reports unknown and is never retried (16.104367ms)
|
||||
ℹ tests 214
|
||||
ℹ suites 0
|
||||
ℹ pass 214
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1593.657114
|
||||
@@ -0,0 +1,109 @@
|
||||
✔ the role file loads and resolves through loadBusiness (7.013486ms)
|
||||
✔ the shipped file is exactly the approved list (1.017721ms)
|
||||
✔ the assistant's authority is message.send within role and nothing else (2.758633ms)
|
||||
✔ the assistant talks to the user and the chief of staff only, and no other shipped role is limited (0.970235ms)
|
||||
✔ a business grant through limits does not widen the role (2.137685ms)
|
||||
✔ a launch block whose by is the assistant is refused (2.228473ms)
|
||||
✔ the instance takes no credential references and no tracker (4.988525ms)
|
||||
✔ an arbiter whose role definition declares messaging lists is refused, for each kind (8.182541ms)
|
||||
✔ an arbiter without messaging lists is accepted beside the limited assistant (3.087549ms)
|
||||
✔ the contract passes work to the chief of staff and directs no one (0.794808ms)
|
||||
✔ config directory and file path follow MOSAIC_CONFIG (1.696288ms)
|
||||
✔ the fixture business validates and comes back frozen (5.395263ms)
|
||||
✔ two instances may share a definition (1.787054ms)
|
||||
✔ top-level refusals (5.608825ms)
|
||||
✔ arbiters and projects (9.533323ms)
|
||||
✔ role instances (4.488864ms)
|
||||
✔ Vikunja bots (9.988719ms)
|
||||
✔ a role without Vikunja takes no tracker block (3.981235ms)
|
||||
✔ credential references match the definition's services (4.485022ms)
|
||||
✔ launch (10.255825ms)
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1 launch.threadIdleSeconds: absent is the default, a positive integer is used, anything else refuses (5.153132ms)
|
||||
✔ loadBusiness: file checks (2.036016ms)
|
||||
✔ loadBusiness: not a regular file (43.529269ms)
|
||||
✔ loading writes nothing (1.001334ms)
|
||||
✔ names that are Object.prototype properties don't count as declared (2.835422ms)
|
||||
✔ the shipped example refuses as written and validates once filled in (0.73839ms)
|
||||
✔ the role file loads and resolves through loadBusiness (7.539373ms)
|
||||
✔ the shipped file is exactly the approved list (0.303623ms)
|
||||
✔ the resolved record matches the approved list, so a widened file fails (2.363825ms)
|
||||
✔ the chief of staff holds role.launch, message.send, role.end and organization.change; every other action is gated (2.131992ms)
|
||||
✔ the pm does not hold role.launch (2.714947ms)
|
||||
✔ a business grant of a gated action through limits does not widen the role (2.225013ms)
|
||||
✔ a limit that drops role.launch leaves the chief of staff with no launch block (1.669963ms)
|
||||
✔ a launch block whose by is the pm instance is refused (2.179706ms)
|
||||
✔ the contract names no duty to talk to the human and the pm contract no launch (0.481765ms)
|
||||
✔ the instance takes no credential references and no tracker (3.071405ms)
|
||||
✔ usage errors exit 4 (303.871635ms)
|
||||
✔ validate: a good business exits 0 and prints instance digests (62.727954ms)
|
||||
✔ validate: project files (314.23983ms)
|
||||
✔ validate: missing files and a broken system config (227.148509ms)
|
||||
✔ validate: credential reference problems exit 2 and name each one (61.90647ms)
|
||||
✔ validate: a token file inside the repository is refused (61.190735ms)
|
||||
✔ validate: role definitions come from MOSAIC_ROLES_DIR (184.076374ms)
|
||||
✔ resolve: prints one instance's record (244.600911ms)
|
||||
✔ resolve: refusals (365.360754ms)
|
||||
✔ parse: exactly one of file or env, plus the service's date (3.282092ms)
|
||||
✔ check: a good file has no problems (0.990988ms)
|
||||
✔ check never opens the file: a write-only token passes (0.498271ms)
|
||||
✔ check: file problems (1.265686ms)
|
||||
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (1.189836ms)
|
||||
✔ check: dates and environment references (0.547118ms)
|
||||
✔ each definition loads, with a contract file, and is exactly the approved list (4.276485ms)
|
||||
✔ an organization file that declares them passes, and each resolves to least privilege (7.565637ms)
|
||||
✔ the chief of staff launches each one; no other role does (4.905878ms)
|
||||
✔ a C-suite role can't be made the launcher: launch.by must hold role.launch (3.074685ms)
|
||||
✔ limits can narrow a C-suite role but never widen it (2.413411ms)
|
||||
✔ any unknown key, tool, action or version in a role file is refused (0.749307ms)
|
||||
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (20.583388ms)
|
||||
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (1.150814ms)
|
||||
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (30.398051ms)
|
||||
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (19.523863ms)
|
||||
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.552575ms)
|
||||
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (18.111688ms)
|
||||
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.944348ms)
|
||||
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (9.89491ms)
|
||||
✔ REQ-PLUG-1 schema: plugins is optional, names known kinds and checked module names (14.226184ms)
|
||||
✔ REQ-PLUG-1 REQ-ROLE-3 the setter writes plugins through the organization writer and logs old and new names (42.969111ms)
|
||||
✔ REQ-PLUG-1 REQ-ROLE-3 the writer checks the bytes it writes: a Date in plugins is refused there too (5.825785ms)
|
||||
✔ REQ-PLUG-1 REQ-ROLE-3 a refused setter write throws a code, changes nothing and logs nothing (2.010915ms)
|
||||
✔ path and load (2.757416ms)
|
||||
✔ refusals (1.578312ms)
|
||||
✔ systemVars flattens the validated config (3.676285ms)
|
||||
✔ precedence: system, business, project, project role, agent (7.269016ms)
|
||||
✔ limits narrow the definition and never widen it (3.312531ms)
|
||||
✔ role.launch stays within-role only for the instance the launch block names (9.436514ms)
|
||||
✔ limits.authority without role.launch leaves the launcher with no launch block (3.311528ms)
|
||||
✔ limits.authority narrows cross-role actions too (1.552642ms)
|
||||
✔ classify (1.329576ms)
|
||||
✔ the record carries what the broker and launcher need (1.420339ms)
|
||||
✔ digest: key order doesn't matter, any value change does (5.97617ms)
|
||||
✔ refusals (2.119726ms)
|
||||
✔ the four shipped version 2 roles load (4.313771ms)
|
||||
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.528072ms)
|
||||
✔ shipped authority follows the note's table (1.494343ms)
|
||||
✔ every role file in roles/ loads through the validator, and a v2 contract exists (1.528304ms)
|
||||
✔ version 1 files keep loading with no authority (1.334174ms)
|
||||
✔ the conductor policy isn't a role (0.256755ms)
|
||||
✔ a missing role file is exit 4, a symbolic link too (0.610671ms)
|
||||
✔ version 2 refusals (2.495921ms)
|
||||
✔ authority: closed vocabulary, no gated-only action, no overlap (3.077829ms)
|
||||
✔ messaging: optional, role definitions and human, never the tester (2.60194ms)
|
||||
✔ credentials: Gitea scopes (2.151533ms)
|
||||
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (2.193417ms)
|
||||
✔ credentials: services (1.308379ms)
|
||||
✔ contract: a non-empty regular Markdown file beside the role file (1.052632ms)
|
||||
✔ every key names known layers and a merge rule (1.344769ms)
|
||||
✔ unknown keys and wrong layers refuse (1.027882ms)
|
||||
✔ types (2.829592ms)
|
||||
✔ merge: defaults, then the most specific layer wins (0.600248ms)
|
||||
✔ merge: limits only narrow, and provenance lists each source (0.471703ms)
|
||||
✔ merge doesn't change its inputs (0.398507ms)
|
||||
ℹ tests 101
|
||||
ℹ suites 0
|
||||
ℹ pass 101
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1900.282934
|
||||
@@ -0,0 +1,216 @@
|
||||
✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (1682.759524ms)
|
||||
✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (68.513626ms)
|
||||
✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (190.52322ms)
|
||||
✔ NO_COLOR: no escape codes, except reverse video on the selected row (280.41727ms)
|
||||
✔ 1-9 shows the decide flow's own lines and question; only y resolves (57.050458ms)
|
||||
✔ the app shows the same decide text as mosaic decide (25.311908ms)
|
||||
✔ any key but y or Y at the question answers No; nothing is sent (219.415653ms)
|
||||
✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (71.357526ms)
|
||||
✔ under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10) (294.036611ms)
|
||||
✔ a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12) (39.395057ms)
|
||||
✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (23.057849ms)
|
||||
✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (416.627001ms)
|
||||
✔ quitting at the question answers No and sends nothing, armed or not (53.140964ms)
|
||||
✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (36.088325ms)
|
||||
✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (54.000394ms)
|
||||
✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (357.475367ms)
|
||||
✔ at every size the decide box shows each line y depends on, or says it is hidden (8605.628397ms)
|
||||
✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (66.060325ms)
|
||||
✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (31.290572ms)
|
||||
✔ help or a detail box cut to fit keeps its last line, Esc included (Dewey) (15.57823ms)
|
||||
✔ keys: views, moving, trail of a selection, the command line, help and quit (78.332308ms)
|
||||
✔ one option: the status line says Press 1, not 1-1 (17.059392ms)
|
||||
✔ no business: the app says so and :business picks one (9.426485ms)
|
||||
✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (1.716027ms)
|
||||
✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (176.813412ms)
|
||||
✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.458642ms)
|
||||
✔ decide with --note still asks; a No sends nothing (43.489785ms)
|
||||
✔ the app's business comes from a live bus host only (1.033942ms)
|
||||
✔ 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.908006ms)
|
||||
✔ draw is pure: same state, same frame (0.766844ms)
|
||||
✔ recordBootRefused keeps the last good load across refusals; recordBootOk clears the refusal (5.051237ms)
|
||||
✔ an unknown key and a JSON syntax error each leave the bus down with the reason and path in the status (909.037758ms)
|
||||
✔ a missing file is recorded too, and a valid file clears the status on the next start (545.912487ms)
|
||||
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (32.550167ms)
|
||||
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (28.298334ms)
|
||||
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (24.957308ms)
|
||||
✔ decide prints a declining choice as declining (30.239199ms)
|
||||
✔ an unknown outcome is reported once and never resent (22.790509ms)
|
||||
✔ a decision closed before the answer arrives exits 2 and points at its trail (23.17867ms)
|
||||
✔ a prefix that matches two open decisions exits 2 and resolves neither (20.583769ms)
|
||||
✔ without --business a command uses the live host's business, and a stale host.json is not a host (22.077404ms)
|
||||
✔ every human command refuses inside an agent run before it touches the bus (17.576987ms)
|
||||
✔ usage errors exit 4; no business and no host is a usage error (22.432638ms)
|
||||
✔ agents and tasks print through the broker (26.138989ms)
|
||||
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.543858ms)
|
||||
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (74.04799ms)
|
||||
✔ trackers come from the tracker.* variables of the one project that names a tracker project (102.795494ms)
|
||||
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (53.494475ms)
|
||||
✔ two projects that each name a tracker project refuse, since the boot shape holds one (52.04302ms)
|
||||
✔ a business without tracker.baseUrl gets no trackers entry (52.453084ms)
|
||||
✔ an unknown business and a broken system config refuse with exit 3 (104.843752ms)
|
||||
✔ follow prints an unsolicited message after plain talk returned (46.574708ms)
|
||||
✔ talk --follow continues after reply and prints a later unsolicited message (48.146865ms)
|
||||
✔ inbox lists all unread human messages under decisions, does not deliver or read them (443.642567ms)
|
||||
✔ follow refuses agent context and bad arguments (12.097712ms)
|
||||
✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (12.901116ms)
|
||||
✔ plain talk leaves the process SIGINT handlers unchanged (16.197893ms)
|
||||
✔ empty views say so (1.057974ms)
|
||||
✔ the trail keeps the broker's order and names a decision's task without its rows (1.333908ms)
|
||||
✔ tasks print the tracker fields the snapshot carries (0.23183ms)
|
||||
✔ the review loop: the coder pushes and asks, the reviewer fetches and approves, and the task's trail holds each step (595.418193ms)
|
||||
✔ refusals: no action, non-fast-forward, an unknown commit or request, and a push for another task moves only that task's ref (359.024356ms)
|
||||
✔ the size cap refuses a push before any byte (195.338139ms)
|
||||
✔ a fetch over the cap refuses before any byte reaches the reviewer (395.572053ms)
|
||||
✔ the socket refuses a request with no capability, an unknown one, and a bad request line (189.973769ms)
|
||||
✔ a rigged clone runs nothing: hooks, fsmonitor, a filter, a gitlink and a symlinked .git/config leave no marker (326.096866ms)
|
||||
✔ an identity with no role, or of another business, refuses not-a-role and moves no ref (232.406123ms)
|
||||
✔ a review of a project the business no longer has refuses unknown-project (208.578459ms)
|
||||
✔ past maxActive connections the socket refuses git-busy, and a hung-up connection frees its slot (135.275034ms)
|
||||
✔ close() answers host-closing to a connection still sending its request line (142.745914ms)
|
||||
✔ a bundle that trickles in refuses body-timeout at its deadline and frees its slot (558.060636ms)
|
||||
✔ a reviewer gone before its bundle was sent is filed fetch-aborted, not as the host's fault (458.855991ms)
|
||||
✔ a reviewer that hangs up the moment it has the whole bundle was served, not aborted (474.80101ms)
|
||||
✔ send: a close after every byte left is served; one that fails a queued write is fetch-aborted (41.791533ms)
|
||||
✔ a bundle with no bytes for bodyIdle refuses body-timeout long before its deadline (457.781088ms)
|
||||
✔ send: a close once the loop is past its last chunk is served, and a failed write there is fetch-aborted (44.804447ms)
|
||||
✔ letGo waits for an answer to leave for at most its cap, then destroys the socket (300.732542ms)
|
||||
✔ a record that fails and can't be undone refuses rollback-failed, which the host logs (427.081408ms)
|
||||
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (985.743283ms)
|
||||
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (202.327823ms)
|
||||
✔ a second host for the same data root refuses with exit 3 while the first runs (104.656482ms)
|
||||
✔ two bus starts racing for one data root leave one host running; the other refuses that a host already runs (573.943298ms)
|
||||
✔ a host takes over a host.lock its dead host left, and puts back a live claim that replaced it meanwhile (1.743234ms)
|
||||
✔ a host.lock that goes between the check and the read is tried again; one that can't be parsed still refuses as unreadable (1.020293ms)
|
||||
✔ a live host's lock stays, however old the file is (0.717977ms)
|
||||
✔ a host.lock left by a host killed with SIGKILL doesn't stop the next bus start (822.480234ms)
|
||||
✔ while a host boots, a reader sees no host: the human commands still ask for --business, stop and status find none (216.438799ms)
|
||||
✔ a host started before host.lock existed, holding only host.json, still refuses a second start, which leaves no lock (28.589563ms)
|
||||
✔ a broker fork that throws leaves no host.lock and no host.json (32.735879ms)
|
||||
✔ a broker send that throws leaves no host.lock and no host.json (28.540395ms)
|
||||
✔ a broker that misses its boot wait leaves no host.lock and no host.json (30.167183ms)
|
||||
✔ a notifier fork that throws stops the broker and leaves no host.lock and no host.json (93.386798ms)
|
||||
✔ a notifier send that throws stops the broker and leaves no host.lock and no host.json (90.660407ms)
|
||||
✔ 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 (91.736723ms)
|
||||
✔ 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 (105.920415ms)
|
||||
✔ a host.json that can't be written stops both children and leaves no host.lock (204.088996ms)
|
||||
✔ an error event on the broker before it replies fails the start at once, and leaves no host.lock and no live child (32.427218ms)
|
||||
✔ an error event on the notifier before it replies fails the start at once, and leaves no host.lock and no live child (92.473277ms)
|
||||
✔ an error event on the broker while the notifier starts fails the start once the notifier replies (207.530858ms)
|
||||
✔ 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 (215.251876ms)
|
||||
✔ 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 (193.160715ms)
|
||||
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1146.97576ms)
|
||||
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (195.397844ms)
|
||||
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (100.245742ms)
|
||||
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (150.53276ms)
|
||||
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (152.829493ms)
|
||||
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (88.937657ms)
|
||||
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (146.905232ms)
|
||||
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (86.096101ms)
|
||||
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (164.548589ms)
|
||||
✔ watchChildren reports a child that died before it was called, and one that dies later (25.558414ms)
|
||||
✔ bus stop refuses to signal a live pid that is not a bus host (203.555487ms)
|
||||
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (255.600379ms)
|
||||
✔ bus start refuses with exit 3 without a notifier config (106.537284ms)
|
||||
✔ bus start runs until bus stop; status reports it while it runs (715.723225ms)
|
||||
✔ bus-service.sh renders the unit and installs it into a given directory (52.251596ms)
|
||||
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (62.980955ms)
|
||||
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (12.698823ms)
|
||||
✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (7.939785ms)
|
||||
✔ each launch rereads limits and model selections; lowered limits keep sessions and log exact hit (44.117124ms)
|
||||
✔ bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest (1333.040809ms)
|
||||
✔ the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code (631.522913ms)
|
||||
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (3607.773137ms)
|
||||
✔ a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left (143.192606ms)
|
||||
✔ a runner that stops at once ends its launch with the runner's reason (216.666335ms)
|
||||
✔ a Claude Code launch with no session credential refuses before it starts (111.795042ms)
|
||||
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (192.030258ms)
|
||||
✔ a runner killed from the host ends its launch as killed, not failed (258.239484ms)
|
||||
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (117.438002ms)
|
||||
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1071.108631ms)
|
||||
✔ 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 (651.948202ms)
|
||||
✔ 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 (3588.804539ms)
|
||||
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (91.858569ms)
|
||||
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (98.89916ms)
|
||||
✔ a launch client that never closes its side doesn't hold the host's close (96.666315ms)
|
||||
✔ a runner that ignores SIGTERM is killed when the host closes (1214.397414ms)
|
||||
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (4.363915ms)
|
||||
✔ migration needs no bus/config, prints whole converted document, is idempotent and never writes (17.545009ms)
|
||||
✔ limits view/set use the human bus transport and pass only the explicit patch (2.136081ms)
|
||||
✔ post-119 example and Jason fixture migrate with checked inherited models; wrong or absent config refuses (321.195996ms)
|
||||
✔ zoned uses the IANA zone across DST (26.870798ms)
|
||||
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (35.463431ms)
|
||||
✔ two blocking decisions get two DMs with different nonces (23.42237ms)
|
||||
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.316422ms)
|
||||
✔ a failed DM is journaled, backs off, and is retried until it lands (26.729677ms)
|
||||
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (28.923624ms)
|
||||
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (35.890716ms)
|
||||
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (25.128557ms)
|
||||
✔ 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 (196.694168ms)
|
||||
✔ a restart after the second refusal does not send before that refusal's 30 min are up (94.605843ms)
|
||||
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (15.545502ms)
|
||||
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (11.704366ms)
|
||||
✔ an inbox read failure is logged and the next poll retries (0.825087ms)
|
||||
✔ no Discord id reaches the journal or the log (11.519014ms)
|
||||
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.161224ms)
|
||||
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.117589ms)
|
||||
✔ the journal: a whole file that is one torn line truncates to empty (1.503539ms)
|
||||
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.678343ms)
|
||||
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.813252ms)
|
||||
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.491511ms)
|
||||
✔ the journal: a symlinked directory refuses and says it is a link (0.399215ms)
|
||||
✔ 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.572514ms)
|
||||
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.482713ms)
|
||||
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.512092ms)
|
||||
✔ digest content stays within Discord's 2000 characters (1.988248ms)
|
||||
✔ runLoop never overlaps ticks and stops after the one in flight (111.451815ms)
|
||||
✔ a human command refused with human-required prints the other-tree line and exits 3 (3.657124ms)
|
||||
✔ a --tester command refused with tester-required prints the other-tree line and exits 3 (1.32098ms)
|
||||
✔ other exit-3 refusals print no other-tree line (1.958298ms)
|
||||
✔ the line names no path (0.169675ms)
|
||||
✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (780.271458ms)
|
||||
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (489.874107ms)
|
||||
✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (491.854544ms)
|
||||
✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (248.432135ms)
|
||||
✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (3303.03907ms)
|
||||
✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (630.771515ms)
|
||||
✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (182.391146ms)
|
||||
✔ install never shows a template name on systemd 261; template drop-ins still refuse (180.319037ms)
|
||||
✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (159.40032ms)
|
||||
✔ inherited mosaic-.service.d override refuses without changing the unit (193.759438ms)
|
||||
✔ inherited service.d override refuses without changing the unit (183.99387ms)
|
||||
✔ inherited drop-in unreadable directory or file refuses before writing (152.750279ms)
|
||||
✔ talk --tester sends a [mos test] message as tester from inside an agent and prints the role's reply (39.395739ms)
|
||||
✔ talk --tester refuses text without the [mos test] prefix before any call (18.616456ms)
|
||||
✔ the read verbs take --tester inside an agent, and the trail shows the tester as the sender (30.841584ms)
|
||||
✔ decide, stop and launches take no --tester (26.519011ms)
|
||||
✔ the tester transport runs tester-cli.mjs, and the tester's refusals exit 3 (0.581474ms)
|
||||
✔ with no stand-in transport, --tester runs tester-cli.mjs on the data root's socket (0.943019ms)
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1 REQ-MSG-1 a thread message starts the thread's session, which stops when idle; its history survives the stop and a restart and no other thread sees it (5537.980984ms)
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1 a running thread counts against its model's limit and an idle-stopped one doesn't (1616.977542ms)
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1 a turn longer than the idle time isn't stopped, and the thread idles out after it (4367.571843ms)
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1 a message sent after the idle snapshot keeps the session, which answers it (2752.196875ms)
|
||||
✔ REQ-ROLE-2 REQ-LAUNCH-1 a thread held by a run the launcher didn't start gets no second session (51.922516ms)
|
||||
✔ REQ-LAUNCH-1 a run that ends without taking its thread's message isn't started again for that message (147.915797ms)
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1 a thread id that isn't a safe segment, or a thread of a non-thread instance, launches nothing (177.681381ms)
|
||||
✔ REQ-THREAD-1 REQ-LAUNCH-1 the chief can't launch the assistant outside a thread (49.70198ms)
|
||||
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 (378.89283ms)
|
||||
✔ the transport writes {business, verb, args} to the child and reads its JSON (58.018681ms)
|
||||
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2254.57468ms)
|
||||
✔ busExit and refuseInsideAgent (0.491937ms)
|
||||
✔ talk sends a REQUEST, prints and reads only the reply to it, and leaves other messages unread (69.247194ms)
|
||||
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1031.635499ms)
|
||||
✔ a late reply the Console has listed stays unread for mosaic inbox, and the next talk skips it (2037.625092ms)
|
||||
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (13.800194ms)
|
||||
✔ launches off and on go to the broker and change the business's launch state (13.56884ms)
|
||||
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (12.931214ms)
|
||||
ℹ tests 206
|
||||
ℹ suites 0
|
||||
ℹ pass 206
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 14902.641606
|
||||
@@ -0,0 +1,98 @@
|
||||
✔ sessionModel: agent vars win, then the system's execution settings (11.752989ms)
|
||||
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (5.483185ms)
|
||||
✔ a chief-of-staff bundle lists launch among the bus tools and the manifest's typed tools (2.817745ms)
|
||||
✔ a chief-of-staff bundle lists end_run and change_organization as typed and nothing untyped, for either harness (6.997344ms)
|
||||
✔ the stop line names end_run for a role.end holder, and every other role keeps today's text (6.733162ms)
|
||||
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.504379ms)
|
||||
✔ the prompt's built-in tools line is the harness's own names, as the manifest lists them (8.929666ms)
|
||||
✔ the prompt names the project clones in the workspace, and none when there are none (7.863011ms)
|
||||
✔ the prompt explains the review loop to a role with push_working, and reviews to one with fetch_review (7.102604ms)
|
||||
✔ a bundle is written once: an existing file refuses (1.593274ms)
|
||||
✔ a path with a single quote can't go into the hook command (1.744125ms)
|
||||
✔ allow exits 0, a deny exits 2 with the reason on stderr (135.989054ms)
|
||||
✔ a missing or wrong policy, or a bad event, exits 2 (83.882583ms)
|
||||
✔ the bundle's wrapped command: a missing gate or node still blocks (1089.577446ms)
|
||||
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (761.835355ms)
|
||||
✔ claude: the hook alone blocks a path outside the workspace (393.311801ms)
|
||||
✔ claude: a second turn resumes the first turn's session (709.900207ms)
|
||||
✔ claude adapter: --restricted is always passed (5.065902ms)
|
||||
✔ claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable (15.022376ms)
|
||||
✔ claude adapter: a directory pin exits 2 before it runs; a pin with a space runs (5.427828ms)
|
||||
✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (674.034862ms)
|
||||
✔ claude: a missing hook or MCP file refuses before claude starts (7.148952ms)
|
||||
✔ pi: policy tools and typed tools pass, anything else is blocked (3.944789ms)
|
||||
✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.963247ms)
|
||||
✔ file tool paths must resolve inside the workspace (2.351712ms)
|
||||
✔ pi's own path normalisation can't be used to step out (1.538202ms)
|
||||
✔ a symlink inside the workspace that points out is outside (1.163984ms)
|
||||
✔ a dangling symlink is refused at any depth, in both harnesses (2.748085ms)
|
||||
✔ read is checked under every spelling pi's read would open, in both harnesses (19.140207ms)
|
||||
✔ other spellings cover directories, dangling links and pi's cwd (1.592041ms)
|
||||
✔ a relative path climbs from the workspace's real path, in both harnesses (5.506639ms)
|
||||
✔ claude path fields per tool (1.052403ms)
|
||||
✔ glob patterns stay inside the workspace (0.628154ms)
|
||||
✔ a path that can't be checked is blocked (0.355507ms)
|
||||
✔ fetch_review's have lists the working refs before origin's, and no more than HAVE_MAX (214.106766ms)
|
||||
✔ fetch_review's have orders the working refs newest first within each clone, one clone after another (129.142114ms)
|
||||
✔ a runner git past its time: SIGTERM reaches its process group, and SIGKILL follows after the grace period (1807.679189ms)
|
||||
✔ once a signalled runner git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (302.872663ms)
|
||||
✔ initialize, ping and tools/list (48.06885ms)
|
||||
✔ tools/call goes through the tool socket; a refusal is an isError result (40.250547ms)
|
||||
✔ replies are compact JSON; over the cap a tool error names the size and the cap (33.254796ms)
|
||||
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (32.530671ms)
|
||||
✔ a missing argument is a usage error (34.192519ms)
|
||||
✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (368.07696ms)
|
||||
✔ pi: a reply over the cap is a tool error naming the size and the cap; list_tasks asks for open tasks (343.068401ms)
|
||||
✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (305.248219ms)
|
||||
✔ pi: a read is refused when pi would open another spelling outside (313.715969ms)
|
||||
✔ pi: a relative path climbs from the real path of a workspace behind a symlink (304.710864ms)
|
||||
✔ pi: a relative path climbs from the real path of a dataRoot behind a symlink (293.969794ms)
|
||||
✔ pi: a missing extension refuses before any model call (5.936834ms)
|
||||
✔ pi: an extension without its configuration fails pi's start (256.217846ms)
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (1.682213ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.239867ms)
|
||||
✔ turnRequest says a tester's message is a test and never Jason's instruction (0.239708ms)
|
||||
✔ turnRequest shows the message's own sent time, a day old as well as new (0.191935ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.255558ms)
|
||||
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (191.215044ms)
|
||||
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (173.857456ms)
|
||||
✔ a task closed on the tracker is missing from the next turn's open list (170.884141ms)
|
||||
✔ a message read a day after it was sent shows the time it was sent (92.878244ms)
|
||||
✔ a decision the human resolves becomes a turn citing the decision and the answer (148.017921ms)
|
||||
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (64.946542ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (51.092973ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (276.111751ms)
|
||||
✔ end_run and change_organization reach the broker with the tool's arguments, and refusals keep their codes (284.109001ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1196.65156ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (94.594498ms)
|
||||
✔ founder credentials stop before the claim (20) (90.472417ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (92.93766ms)
|
||||
✔ the launch ending under a running session exits 22 (102.381526ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (199.927245ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (47.468955ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (148.21306ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (80.539326ms)
|
||||
✔ the PM gets its task verbs and the reads, and no launch (10.445132ms)
|
||||
✔ the chief of staff gets launch, send_message, end_run and change_organization, and no task verbs (2.203035ms)
|
||||
✔ end_run sends role and reason, no decision; change_organization sends revision and the organization keys (2.916438ms)
|
||||
✔ the organization keys change_organization takes are the broker's ORGANIZATION_KEYS (0.147141ms)
|
||||
✔ a role without role.end or organization.change gets neither tool, and each tool follows its own grant (11.443134ms)
|
||||
✔ the assistant gets send_message and the reads, no launch (2.212377ms)
|
||||
✔ list_requirements reads the requirement ids, for a task.create holder with a tracker (2.941407ms)
|
||||
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (1.157381ms)
|
||||
✔ a coder with a tracker gets its own task tools and no task_reassign (1.223648ms)
|
||||
✔ launch only when the business's launch block names the instance as launcher (2.974238ms)
|
||||
✔ an action outside the instance's authority has no tool (2.034293ms)
|
||||
✔ the review loop: push_working and request_review for the coder, fetch_review and review_verdict for the reviewer, none for the PM (5.694429ms)
|
||||
✔ callTool: one JSON line out, the result back, a refusal rejects (10.23694ms)
|
||||
✔ list_tasks takes the broker's filters and pages; get_task takes a task_ref (2.582732ms)
|
||||
✔ callTool sends list_tasks done false unless the session names done; other tools as given (2.473225ms)
|
||||
✔ replyText: compact JSON up to the cap, an error naming the size and the cap over it (0.817834ms)
|
||||
ℹ tests 90
|
||||
ℹ suites 0
|
||||
ℹ pass 90
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 10868.805917
|
||||
@@ -0,0 +1,29 @@
|
||||
✔ REQ-PLUG-1 REQ-LIMIT-4 the registry: usage-source and limit-check at interface version 1, each a list (0.985706ms)
|
||||
✔ REQ-PLUG-1 checkPlugins accepts one key per kind, each a list of names, empty included (0.225212ms)
|
||||
✔ REQ-PLUG-1 checkPlugins refuses bad shapes, unknown kinds, bad names and duplicates (4.143343ms)
|
||||
✔ REQ-PLUG-1 a kind that switches on one module takes one name, not a list (0.152922ms)
|
||||
✔ REQ-PLUG-1 checkPlugins reports through the caller's fail callback (0.138ms)
|
||||
✔ REQ-PLUG-1 fixture modules of each kind switch on from a test organization file, no core file changed (7.491526ms)
|
||||
✔ REQ-PLUG-1 a name not matching ^[a-z][a-z0-9-]*$ refuses and keeps the old setting (3.356649ms)
|
||||
✔ REQ-PLUG-1 a missing module directory or index.mjs refuses and keeps the old setting (3.177898ms)
|
||||
✔ REQ-PLUG-1 a symlinked module directory or index.mjs refuses and keeps the old setting (2.432663ms)
|
||||
✔ REQ-PLUG-1 a symlinked kind directory refuses and keeps the old setting (2.058099ms)
|
||||
✔ REQ-PLUG-1 a symlinked packages/plugins refuses (run with --preserve-symlinks so the path keeps it) (28.395577ms)
|
||||
✔ REQ-PLUG-1 index.mjs swapped for a symlink after the lstat checks refuses at the realpath check (2.848056ms)
|
||||
✔ REQ-PLUG-1 a path that can't be read refuses as unreadable, is logged and keeps the old setting (2.794748ms)
|
||||
✔ REQ-PLUG-1 a path that becomes unreadable after the lstat checks refuses at either realpath (2.459882ms)
|
||||
✔ REQ-PLUG-1 a module that fails to load refuses and keeps the old setting (3.058964ms)
|
||||
✔ REQ-PLUG-1 a missing export refuses and keeps the old setting (3.229696ms)
|
||||
✔ REQ-PLUG-1 a different interface version refuses and keeps the old setting (2.385959ms)
|
||||
✔ REQ-PLUG-1 a kind refused on its first load has nothing switched on, and the log names kind and name (1.751521ms)
|
||||
✔ REQ-PLUG-1 the setting's shape: unknown kinds refuse alone, a bad whole setting keeps everything (2.942538ms)
|
||||
✔ REQ-PLUG-1 loads run one at a time, so the last requested setting wins (103.17678ms)
|
||||
✔ REQ-PLUG-1 the loader needs a log callback (1.187283ms)
|
||||
ℹ tests 21
|
||||
ℹ suites 0
|
||||
ℹ pass 21
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 228.585762
|
||||
@@ -0,0 +1,148 @@
|
||||
✔ a first launch clones the root at its branch, with the root's path as origin (34.445672ms)
|
||||
✔ the clone is not at its place until git clone has finished (22.677148ms)
|
||||
✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (61.361316ms)
|
||||
✔ a clone ahead of the root keeps its commits and counts as current (37.462985ms)
|
||||
▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves
|
||||
✔ a modified file (43.533923ms)
|
||||
✔ an untracked file (43.129621ms)
|
||||
✔ a staged file (43.869603ms)
|
||||
✔ another branch (42.774274ms)
|
||||
✔ a detached HEAD (42.115221ms)
|
||||
✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (216.546006ms)
|
||||
✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (42.324779ms)
|
||||
▶ a clone kept before the branch check, or for a failed status, gets no fetch
|
||||
✔ not-a-clone (22.716351ms)
|
||||
✔ hooks-present (24.651417ms)
|
||||
✔ submodule (30.767282ms)
|
||||
✔ config-changed (27.426428ms)
|
||||
✔ root-unreadable (27.265225ms)
|
||||
✔ root-detached (34.834562ms)
|
||||
✔ status-failed, on the root's branch and clean (37.383519ms)
|
||||
✔ status-failed, dirty (38.333815ms)
|
||||
✔ a clone kept before the branch check, or for a failed status, gets no fetch (244.632931ms)
|
||||
✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (41.055053ms)
|
||||
✔ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (48.821648ms)
|
||||
✔ a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge (75.601985ms)
|
||||
▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it
|
||||
✔ user.name (27.339069ms)
|
||||
✔ user.email (25.658383ms)
|
||||
✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (53.509168ms)
|
||||
✔ a commit git can't read after the fast-forward is null (53.819923ms)
|
||||
✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (28.477426ms)
|
||||
▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it
|
||||
✔ core.fsmonitor (49.416135ms)
|
||||
✔ core.hooksPath (36.295988ms)
|
||||
✔ a hook file (33.515145ms)
|
||||
✔ include.path (33.302193ms)
|
||||
✔ includeIf (33.52503ms)
|
||||
✔ a filter driver (33.803418ms)
|
||||
✔ origin moved (33.735133ms)
|
||||
✔ a second origin url (33.042174ms)
|
||||
✔ the origin url twice, both the root (34.23918ms)
|
||||
✔ an ext:: origin (34.00221ms)
|
||||
✔ a branch tracking another remote (33.549815ms)
|
||||
✔ a branch merging a tag (34.36545ms)
|
||||
✔ a fetch line onto local branches (32.596965ms)
|
||||
✔ no origin (33.857518ms)
|
||||
✔ a config file over the size cap (30.735345ms)
|
||||
✔ config a symlink to /dev/zero (29.547432ms)
|
||||
✔ the clone a symlink to the bait (31.493236ms)
|
||||
✔ the clone a file (31.355369ms)
|
||||
✔ the clone a plain directory (30.914747ms)
|
||||
✔ .git a gitdir: file (30.843288ms)
|
||||
✔ .git a symlink (30.791882ms)
|
||||
✔ .git/logs a symlink (32.113352ms)
|
||||
✔ .git/refs/heads a symlink (32.121261ms)
|
||||
✔ .git/objects a symlink (30.275442ms)
|
||||
✔ a reflog hard-linked to a file outside (30.094086ms)
|
||||
✔ HEAD a fifo (31.856ms)
|
||||
✔ a commondir file (31.206942ms)
|
||||
✔ an alternates file (31.234399ms)
|
||||
✔ a gitlink staged, no .gitmodules (44.631198ms)
|
||||
✔ a gitlink and .gitmodules staged (45.484498ms)
|
||||
✔ a gitlink to a repository with its own gitlink (52.252487ms)
|
||||
✔ a gitlink and .gitmodules committed (48.059005ms)
|
||||
✔ a gitlink and submodule.active in config (45.036037ms)
|
||||
✔ a .git/modules with no gitlink (33.46943ms)
|
||||
✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1197.533206ms)
|
||||
▶ a root that can't be cloned refuses with the project id and leaves nothing behind
|
||||
✔ missing (10.463177ms)
|
||||
✔ not a repository (10.210332ms)
|
||||
✔ a plain directory inside another repository (14.63898ms)
|
||||
✔ unreadable (11.094884ms)
|
||||
✔ on no branch (13.212797ms)
|
||||
✔ missing an object (16.54264ms)
|
||||
✔ a root that can't be cloned refuses with the project id and leaves nothing behind (77.120236ms)
|
||||
✔ a clone whose root is on no branch, or gone, is kept and named (39.988934ms)
|
||||
✔ no credential or forge remote of the root reaches a clone (50.826188ms)
|
||||
✔ every project in the business file gets its own clone at its own branch (34.497608ms)
|
||||
✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (514.398658ms)
|
||||
✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1016.717925ms)
|
||||
✔ a timeout signals git's process group, so a process git started dies with it (515.711284ms)
|
||||
✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (517.43074ms)
|
||||
✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (1.631403ms)
|
||||
✔ sandboxProblem names a missing bwrap and a bwrap that fails (8.352149ms)
|
||||
✔ every bind says why; the bundle and session file stay read-only over the run directory (2.182452ms)
|
||||
✔ REQ-THREAD-2 a thread store binds its session directory and Claude transcripts, and nothing more (0.842215ms)
|
||||
✔ the host's git socket is bound only when given, read-only and before the writable binds (0.672309ms)
|
||||
✔ the git identity is the instance's, at <instance>@<business>.mosaic.invalid, and sandboxArgv refuses without it (1.023217ms)
|
||||
✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (1868.471938ms)
|
||||
✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2715.621302ms)
|
||||
✔ a confined session that runs tester-cli.mjs is refused as tester, and the same shim outside the sandbox binds (245.402923ms)
|
||||
✔ resolveSeat: by name under --repo resolves the repo layout (1.235927ms)
|
||||
✔ resolveSeat: by path resolves the fleet layout (0.349437ms)
|
||||
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.727754ms)
|
||||
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.646712ms)
|
||||
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.81063ms)
|
||||
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (0.863419ms)
|
||||
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.130459ms)
|
||||
✔ CLI launch: registers, execs the fake launch script, and passes args through (27.14557ms)
|
||||
✔ CLI launch: --harness lands in the record (31.249426ms)
|
||||
✔ CLI launch: the launch script's own exit code passes through (29.11648ms)
|
||||
✔ CLI launch: relaunching a seat rewrites the one registration record (52.442086ms)
|
||||
✔ CLI launch: omitting --task records an empty string, not null (29.349373ms)
|
||||
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (84.103261ms)
|
||||
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (126.367353ms)
|
||||
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.369458ms)
|
||||
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.609343ms)
|
||||
✔ 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.682598ms)
|
||||
✔ updateTask: records setBy, preserves startedAt and every unrelated field, upgrades an old record in place only when the task is set, and replaces a previous attribution (0.84589ms)
|
||||
✔ 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 (256.164119ms)
|
||||
✔ REQ-LIMIT-1: family substring matching is no longer exported (0.775025ms)
|
||||
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.673832ms)
|
||||
✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.248967ms)
|
||||
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.53489ms)
|
||||
✔ session file and launch log: 0600, the session file written once (1.022927ms)
|
||||
✔ endReason maps the runner's exit codes; a signal is killed (0.116847ms)
|
||||
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.190148ms)
|
||||
✔ REQ-THREAD-1 Registry.running keys a thread run by business, instance and thread (0.591391ms)
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1 threadStore checks both segments before the join and refuses an unsafe directory (1.353679ms)
|
||||
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (0.926597ms)
|
||||
✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (195.824931ms)
|
||||
✔ workRef and validBranch take only what a ref name can hold (1.121906ms)
|
||||
✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (100.087337ms)
|
||||
✔ a rewritten branch refuses non-fast-forward and the work ref stays (84.335174ms)
|
||||
✔ a branch with no history in common with main refuses unrelated-history (44.813686ms)
|
||||
✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (38.885845ms)
|
||||
✔ main is fetched from the root again before each push, so a clone of a newer root pushes (93.074075ms)
|
||||
✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (60.522192ms)
|
||||
✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (58.462258ms)
|
||||
✔ only a v2 bundle with the named branch is read (44.107116ms)
|
||||
✔ a branch named main, or named for another task, moves only this task's work ref (75.866021ms)
|
||||
✔ a record that throws moves the work ref back, or removes a new one (108.110768ms)
|
||||
✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (402.6044ms)
|
||||
✔ a root that can't be read refuses root-unreadable (27.764316ms)
|
||||
✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2114.828552ms)
|
||||
✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1028.522439ms)
|
||||
✔ a working repository path that isn't a directory refuses (26.148202ms)
|
||||
✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (115.050649ms)
|
||||
✔ review of a project with no working repository refuses unknown-commit (17.883073ms)
|
||||
✔ the runner's git tools hold the host's limits and branch rule (1.509335ms)
|
||||
ℹ tests 135
|
||||
ℹ suites 0
|
||||
ℹ pass 135
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 4979.732089
|
||||
@@ -0,0 +1,105 @@
|
||||
✔ the boot config is checked before anything starts (28.070166ms)
|
||||
✔ a business with no tracker entry refuses task verbs (17.352061ms)
|
||||
✔ credential.expiring and .expired are recorded once per instance (34.038475ms)
|
||||
✔ a token file that changes on disk records credential.changed (14.876411ms)
|
||||
✔ autostart polls, reconciles and retries a startup the tracker was down for (39.750394ms)
|
||||
✔ a refusal a restart must clear is not retried by the poll (12.008503ms)
|
||||
✔ a poll that fires while two are queued is dropped (15.150673ms)
|
||||
✔ close waits for a running verb and refuses one that has not started (44.995433ms)
|
||||
✔ stopped bus resumes an empty task cursor after a long outage, exactly once (47.359648ms)
|
||||
✔ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (36.054963ms)
|
||||
✔ commit followed by crash before cache advance neither loses nor duplicates a comment (23.602579ms)
|
||||
✔ failed event insert rolls back cursor and a retry still emits the comment once (19.976097ms)
|
||||
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (17.514018ms)
|
||||
✔ the bundled Vikunja is the pinned upstream image the runbook names (1.113175ms)
|
||||
✔ every published port is on 127.0.0.1, and no secret is in the file (0.411928ms)
|
||||
✔ the committed table loads frozen; its verbs are the broker task verbs (1.347026ms)
|
||||
✔ a write names a verb the table has as its own key, not one it inherits (0.500482ms)
|
||||
✔ the schema refuses any other shape (2.190653ms)
|
||||
✔ the Field table in the slice 1 plan names the verbs each field is written by (0.578205ms)
|
||||
✔ each verb refuses, asks for a decision and writes as the table and the role files say (200.152011ms)
|
||||
✔ the list holds the PRD's requirement ids, in its order, at its version (1.869371ms)
|
||||
✔ the committed list loads frozen (0.914196ms)
|
||||
✔ the schema refuses any other shape (4.321899ms)
|
||||
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (117.302255ms)
|
||||
✔ the project list holds recorded project objects in the recorded list envelope (12.741295ms)
|
||||
✔ the recorded task bodies pass the checks S3 applies to every read (0.479645ms)
|
||||
✔ the client works against the fake over real HTTP with the platform fetch (99.296281ms)
|
||||
✔ a correct install starts, and the first reconcile records tasks that already exist (31.266825ms)
|
||||
✔ verbs refuse while a business is starting and after startup refused it (18.091654ms)
|
||||
✔ startup refuses a token that can do more than its role needs (47.560139ms)
|
||||
✔ startup refuses an unsupported version and flags an untested one (36.449814ms)
|
||||
✔ startup refuses a board that the runbook did not install (45.075708ms)
|
||||
✔ startup refuses a project the sync bot cannot read (9.60747ms)
|
||||
✔ startup refuses a configured label the pm bot cannot see (10.705305ms)
|
||||
✔ startup refuses an expired credential and a missing sync credential (17.138976ms)
|
||||
✔ an unreachable tracker refuses with tracker-unavailable (8.699981ms)
|
||||
✔ an edit in the UI is recorded once, with the fields that changed (83.482499ms)
|
||||
✔ a move between open buckets is seen on the board, though updated does not change (48.176564ms)
|
||||
✔ a person's comment is counted and a bot's is not (77.321362ms)
|
||||
✔ the hourly reconcile catches a comment through comment_count (80.732472ms)
|
||||
✔ a task closed in the UI leaves the open view with its done bucket (117.169211ms)
|
||||
✔ a task that leaves the board is recorded as deleted, moved or out of reach (66.752538ms)
|
||||
✔ a poll that read before a verb wrote does not overwrite the verb (37.406098ms)
|
||||
✔ a tracker fault during a tick is reported and the next tick catches up (35.479585ms)
|
||||
✔ a malformed answer refuses the tick with tracker-shape (15.490012ms)
|
||||
✔ no token value reaches the database, the log or a refusal (48.425619ms)
|
||||
✔ the first look at a task counts only comments inside the window (35.20304ms)
|
||||
✔ with descendants off the adapter reads no project list and no child, as in v0 (55.082193ms)
|
||||
✔ a three-level tree is read per project, and a project outside it is not (24.695733ms)
|
||||
✔ a watch-only child keeps the business ready, and a failed child read refuses as transient (31.701681ms)
|
||||
✔ a root without the installed buckets refuses with descendants on (11.279239ms)
|
||||
✔ the project list refuses without read_all, and skips archived projects and saved filters (27.456213ms)
|
||||
✔ descendants must be true or false (11.11059ms)
|
||||
✔ a move inside the tree is a moved tombstone plus a snapshot under the new ref (62.808844ms)
|
||||
✔ a child made after startup joins the tree at the next reconcile (56.297912ms)
|
||||
✔ a child that leaves the tree has its tasks recorded missing, and the ledger keeps their history (57.823847ms)
|
||||
✔ a child deleted or hidden between reconciles drops out at the next tick, and the root still refuses (83.048228ms)
|
||||
✔ a reconcile that fails keeps the previous tree (35.3905ms)
|
||||
✔ a closed task the cursor missed takes its own project's done bucket (34.345936ms)
|
||||
✔ closed tasks read at startup take their own project's done bucket (12.137554ms)
|
||||
✔ a closed task the cursor returns takes its own project's done bucket (34.71576ms)
|
||||
✔ a task moved out of a child that leaves the tree at the same time is moved, not no-access (31.39849ms)
|
||||
✔ a task in a bucket outside the five keeps its raw bucket id (31.255788ms)
|
||||
✔ bucket 0 on a watch-only task never becomes a bucket or a state (80.546633ms)
|
||||
✔ a child whose kanban view is deleted still refuses the tick, and the reconcile reads it watch-only (57.433982ms)
|
||||
✔ a child dropped at a tick comes back at the next reconcile once the bot reads it again (33.880991ms)
|
||||
✔ task.create needs a recorded human request and a requirement id (42.51081ms)
|
||||
✔ task.create takes only an approved requirement id (30.511779ms)
|
||||
✔ task.create cites the request a received message carries, not the message's id (24.9248ms)
|
||||
✔ only labels named in the business file can be written (32.145566ms)
|
||||
✔ task.schedule sets and clears a due date and relations (39.921432ms)
|
||||
✔ assign and reassign move the role bots and record task.assigned (54.241968ms)
|
||||
✔ task.update.assigned is for the assignee and records task.state (49.918188ms)
|
||||
✔ a wrong expected digest records task.conflict and writes nothing (19.171031ms)
|
||||
✔ a cross-role verb needs a resolved decision, used once (25.408393ms)
|
||||
✔ task.close needs a verdict; after it every verb refuses with task-done (23.86341ms)
|
||||
✔ a task with pushed work closes only citing an approve verdict for its latest push, and a refusal writes nothing (32.8342ms)
|
||||
✔ a lost answer is settled by a re-read and never retried (25.899763ms)
|
||||
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (38.812586ms)
|
||||
✔ a task the sync bot cannot read refuses and records nothing (18.374045ms)
|
||||
✔ verbs and polls for one business run one at a time (85.883766ms)
|
||||
✔ a due date with milliseconds is written to the second (66.841605ms)
|
||||
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (37.491295ms)
|
||||
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (14.365465ms)
|
||||
✔ a create whose final read fails succeeds and records task.created (34.316773ms)
|
||||
✔ an edit between the last write and the final read shows as external on the next poll (37.022381ms)
|
||||
✔ task.created is recorded when a later label write fails (13.628346ms)
|
||||
✔ every verb acts in an installed child, on the child's own board (47.982514ms)
|
||||
✔ task.create refuses a project outside the tree, a watch-only one and one that is not an id (26.634ms)
|
||||
✔ with descendants off, task.create takes only the root project (12.967139ms)
|
||||
✔ a verb on a watch-only task, or a relation to one, refuses with project-not-installed (20.508868ms)
|
||||
✔ a related task is read before authority: in another project than its ref names, task-moved; missing, task-not-found (25.408008ms)
|
||||
✔ a task moved to another project of the tree refuses its old ref with task-moved and takes its new one (16.786835ms)
|
||||
✔ a verb and a create in the child record the final read of the child's board (15.140953ms)
|
||||
✔ a create in the child whose final read fails records the child's todo bucket (34.28389ms)
|
||||
✔ a lost answer in the child is settled by a re-read of the child's board (22.532406ms)
|
||||
✔ every related task is read before authority, not only the first (18.127432ms)
|
||||
ℹ tests 97
|
||||
ℹ suites 0
|
||||
ℹ pass 97
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1105.802003
|
||||
@@ -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 '8e2c5c2d03f599451bfce087a19be1fe56c80003'.
|
||||
|
||||
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 '8e2c5c2d03f599451bfce087a19be1fe56c80003'.
|
||||
|
||||
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,82 @@
|
||||
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/copy-service.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/offline-check.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 183)
|
||||
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
|
||||
OK pinned render runs copy CLI with copy cwd/repo and no checkout executable
|
||||
OK pinned install writes the rendered unit in a temporary unit directory
|
||||
OK pinned install refuses short prerequisite and writes no unit
|
||||
OK pinned install refuses missing prerequisite and writes no unit
|
||||
OK pinned install refuses cli prerequisite and writes no unit
|
||||
OK pinned install refuses pi prerequisite and writes no unit
|
||||
OK status names the copy from fake systemctl effective cwd
|
||||
OK checkout install explicitly recommends --copy
|
||||
OK wrapper check --offline passes local fixture and makes no network call
|
||||
OK wrapper run --offline refuses usage
|
||||
|
||||
discord suite: 78 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: 1174380 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,86 @@
|
||||
OK args: exit 2, target-branch
|
||||
OK args: exit 2, target-branch
|
||||
OK args: exit 4
|
||||
OK args: exit 4
|
||||
OK args: exit 4
|
||||
OK --base next refuses before the config is read (got 2)
|
||||
OK a missing system config exits 3 (got 3)
|
||||
OK nobus: exit 3
|
||||
OK schema-table: exit 2, bus-schema
|
||||
OK schema-meta: exit 2, bus-schema
|
||||
OK openBus opens the database read-only
|
||||
OK other-business: exit 2, decision-not-found
|
||||
OK no-raise: exit 2, decision-not-found
|
||||
OK not-a-merge: exit 2, not-a-merge
|
||||
OK open: exit 2, not-resolved
|
||||
OK withdrawn: exit 2, not-resolved
|
||||
OK by-agent: exit 2, not-human
|
||||
OK via-webui: exit 2, not-human
|
||||
OK no-answer: exit 2, not-human
|
||||
OK answer-role: exit 2, not-human
|
||||
OK answer-run: exit 2, not-human
|
||||
OK answer-choice: exit 2, not-human
|
||||
OK two-answers: exit 2, not-human
|
||||
OK chose-no: exit 2, not-merge-option
|
||||
OK two-refs: exit 2, work-ref
|
||||
OK bad-ref: exit 2, work-ref
|
||||
OK task-ref-disagrees: exit 2, work-ref
|
||||
OK raise-task-disagrees: exit 2, work-ref
|
||||
OK target-main: exit 2, target-branch
|
||||
OK superseded-question: exit 2, superseded
|
||||
OK superseded-task: exit 2, superseded
|
||||
OK superseded-by: exit 2, superseded
|
||||
OK no-verdict: exit 2, no-verdict
|
||||
OK changes-later: exit 2, not-approved
|
||||
OK short-commit: exit 2, not-approved
|
||||
OK wrong-task-body: exit 2, not-approved
|
||||
OK args: exit 2, decision-not-found
|
||||
OK earlier-merge: exit 0
|
||||
OK ref-moved: exit 2, ref-moved
|
||||
OK root-top: exit 2, root-file
|
||||
OK root-newdir: exit 2, root-file
|
||||
OK root-src: exit 2, root-file
|
||||
OK unrelated: exit 2, base-conflict
|
||||
OK empty: exit 2, base-conflict
|
||||
OK an empty reviewed diff says so
|
||||
OK dirty-changed: exit 2, checkout-dirty
|
||||
OK dirty-staged: exit 2, checkout-dirty
|
||||
OK dirty-untracked: exit 2, checkout-dirty
|
||||
OK conflict: exit 2, base-conflict
|
||||
OK a refusal after the worktree is added leaves no worktree or scratch
|
||||
OK cleanup looks for the worktree's Docker network
|
||||
OK dirty-during: exit 2, checkout-dirty
|
||||
OK a reviewed path dirtied during the gate moves no ref
|
||||
OK drift: exit 2, patch-drift
|
||||
OK patch-drift leaves no worktree or scratch
|
||||
OK gate-pkg: exit 2, gate-failed
|
||||
OK gate-failed names the failing step's log, and it exists
|
||||
OK gate-failed leaves no worktree or scratch
|
||||
OK gate-script: exit 2, gate-failed
|
||||
OK a failing scripts/test-*.sh fails the gate
|
||||
OK a failed gate moves no ref
|
||||
OK nomods: exit 4
|
||||
OK a target without node_modules leaves no worktree or scratch
|
||||
OK moved: exit 2, base-moved
|
||||
OK base-moved keeps the base where it moved to
|
||||
OK dry: exit 0
|
||||
OK --dry-run writes no ref
|
||||
OK --dry-run prints the commit with --subject and --attribution
|
||||
OK --dry-run runs the gate
|
||||
OK --dry-run leaves no worktree or scratch
|
||||
OK land: exit 0
|
||||
OK the landing is one commit on the old base
|
||||
OK its diff is the reviewed diff by patch-id
|
||||
OK its tree is the dry run's
|
||||
OK the message has the option's subject, the citations and the attribution last
|
||||
OK it prints the line for the PM
|
||||
OK the checked-out base has the reviewed paths and keeps the unrelated edit
|
||||
OK only refs/heads/refactor exists in the target
|
||||
OK the work ref is unchanged
|
||||
OK the bus database is unchanged
|
||||
OK the gate ran each package suite but queue, and each scripts/test-*.sh
|
||||
OK the landing leaves no worktree or scratch
|
||||
OK the target's node_modules is still there, with its contents
|
||||
OK the worktree's Docker network is removed
|
||||
---
|
||||
land-work selftests: 84 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 (/mnt/storage/scratch/researcher/q142) 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,123 @@
|
||||
✔ 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 (5230.934207ms)
|
||||
✔ 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 (7231.758558ms)
|
||||
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3696.986139ms)
|
||||
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
|
||||
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4344.430887ms)
|
||||
✔ the README says a refused field table names the file or the key at fault, never a path (1.298352ms)
|
||||
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (9231.5796ms)
|
||||
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (225.859767ms)
|
||||
✔ decisions route serves the roles and recent lists from the reader view (512.288433ms)
|
||||
✔ credentials route serves the broker credential status from the reader view (311.483522ms)
|
||||
✔ replies route serves the replies to one message from the reader view (349.77839ms)
|
||||
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (465.101965ms)
|
||||
✔ bus refusals map to statuses and never carry a path or a stack (311.583102ms)
|
||||
✔ humanCall speaks the human transport protocol without blocking the server (2398.592279ms)
|
||||
✔ busReader takes --business, else the live bus host, else refuses (700.349317ms)
|
||||
✔ humanCall kills a transport that runs past its timeout (555.114006ms)
|
||||
✔ serve refuses a --business value that is not a business id (1068.402201ms)
|
||||
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3536.143478ms)
|
||||
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3201.148226ms)
|
||||
✔ conversation view: a newer session with no readable history keeps the marker (1989.415585ms)
|
||||
✔ conversation view: seats without history say so and offer no reply (1440.986308ms)
|
||||
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3228.134554ms)
|
||||
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54900.616855ms)
|
||||
✔ messages HTTP read pages and preserves metadata; bad arguments refuse before a read; other reads unchanged (364.038412ms)
|
||||
✔ 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 (4467.978378ms)
|
||||
✔ Messages nav count polls on Tasks and Board and clears on a Board refusal; messages 403 refuses local pages (4051.746492ms)
|
||||
✔ copy writes the Console paths at the full id, read-only, and leaves an existing copy alone (4438.261986ms)
|
||||
✔ copy refuses a commit off refactor, an unknown one, a worktree and a bad copies directory (2782.818285ms)
|
||||
✔ copy writes nothing when an archived path is missing at the commit (604.301242ms)
|
||||
✔ a copy that loses the rename race leaves no read-only temp directory behind (1147.728327ms)
|
||||
✔ a copy whose rename fails with no target refuses, writes no copy and leaves no temp directory (1263.728136ms)
|
||||
✔ a copy that meets a target holding files says exists: and moves nothing into it (411.148264ms)
|
||||
✔ a copy that meets an empty target directory replaces it by rename, not by moving into it (389.060675ms)
|
||||
✔ serve runs from a copy: /healthz 200, the config check, roles and RELEASE from the copy, the queue from --checkout (831.471315ms)
|
||||
✔ serve refuses a --checkout that is not an absolute directory path (222.421843ms)
|
||||
✔ consoleReads runs the queue read under queueRoot and reads RELEASE under root (52.055686ms)
|
||||
✔ preview options are all three or none, and each is checked (10.61799ms)
|
||||
✔ the CLI refuses a partial or invalid preview flag set at start (3184.911989ms)
|
||||
✔ the CLI with all three flags serves the preview mode (624.186715ms)
|
||||
✔ a request for the public name needs the header equal to the user id, else 401 and nothing read (94.366174ms)
|
||||
✔ the header name matches without regard to case, and the value compare is exact (18.590775ms)
|
||||
✔ the public name with the header reads the bus views, and the Host and Origin are checked first (82.708357ms)
|
||||
✔ in preview mode every write answers 403 read-only, from the public name and loopback, and reaches nothing (28.489194ms)
|
||||
✔ loopback requests are unchanged in preview mode, and the bus views work with the board down (10.482668ms)
|
||||
✔ a proxy that rewrites Host to the loopback port gets 403 on its first request, whatever the user (39.189779ms)
|
||||
✔ a repeated Host is 400 before any other check, in preview mode and without it (25.523412ms)
|
||||
✔ without the preview flags the public name is refused and writes still reach the board (8.807127ms)
|
||||
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.312979ms)
|
||||
✔ credential state from the date alone, as the bus states it (0.2821ms)
|
||||
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2612.050916ms)
|
||||
✔ queue: a refused read fails closed with the store's text, redacted (1175.880108ms)
|
||||
✔ queue: a store that fails to load, or a child that dies, gives a fixed message with no path or stack (2097.564356ms)
|
||||
✔ business: names, vars on the allowlist, authority, credential metadata only (29.270881ms)
|
||||
✔ business: missing or invalid file refuses with the module's text, redacted (51.87518ms)
|
||||
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (45.43715ms)
|
||||
✔ settings: release, board, loopback address, notifier binding name only (938.712979ms)
|
||||
✔ task fields: the committed table with no reads configured, GET only; a bad table refuses with no path (203.710367ms)
|
||||
✔ bus-status: a refused boot shows reason, file and last good load; a live host or an ok boot clears it (61.727626ms)
|
||||
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3227.371752ms)
|
||||
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22706.683945ms)
|
||||
✔ 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 (7851.327411ms)
|
||||
✔ a sent receipt lists its replies as the page refreshes, oldest first and capped, and marks none delivered or read (11743.940094ms)
|
||||
✔ startServer takes send only as a function and only with the preview mode (10.328605ms)
|
||||
✔ the CLI refuses --send without the preview flags, and without a configured bus (1245.583409ms)
|
||||
✔ the CLI with --send and a configured bus sends through the human transport and logs one line (443.22223ms)
|
||||
✔ without --send the route is read-only like every other write, and config says no send (15.087264ms)
|
||||
✔ with --send every other write is still 403 read-only, and nothing reaches the board or bus (25.109901ms)
|
||||
✔ config says send only on the public name with --send on (5.042224ms)
|
||||
✔ a send without the signed-in user is 401 and is neither sent nor logged, loopback included (7.383209ms)
|
||||
✔ each request check refuses before anything is sent, and logs the refusal without the body (28.567821ms)
|
||||
✔ a send passes exactly to, body and in_reply_to, answers the id, and logs it (6.442959ms)
|
||||
✔ a crafted role name cannot forge a log line (6.366008ms)
|
||||
✔ a bus refusal is not sent, with its status; an outcome the bus could not confirm is unknown and never retried (19.653178ms)
|
||||
✔ through the human transport the request is exactly the business, message.send and the keys (257.616095ms)
|
||||
✔ against a real broker: the message is written once as a human send, and its refusals map (145.687592ms)
|
||||
✔ loopback host and board origin fail closed (14.839573ms)
|
||||
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (274.85559ms)
|
||||
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (123.105217ms)
|
||||
✔ unreachable board reports URL; CLI rejects unsupported options (1144.094061ms)
|
||||
✔ render prints the unit without the preview flags (56.719074ms)
|
||||
✔ render carries every serve flag, the preview set included (123.360635ms)
|
||||
✔ a partial preview set is refused (113.078951ms)
|
||||
✔ a value the unit cannot carry is refused, for every flag (2116.712978ms)
|
||||
✔ the rendered unit names none of the human proof agent markers and sets no agent variable (254.446287ms)
|
||||
✔ install into --dir with --no-reload writes the file whole, then reports unchanged (677.418366ms)
|
||||
✔ install without --no-reload runs daemon-reload and nothing that starts the unit (88.503029ms)
|
||||
✔ uninstall refuses while the unit is running, with or without --no-reload, and removes it once it is not (505.521248ms)
|
||||
✔ status prints the unit state and GET /healthz on the installed port (444.819164ms)
|
||||
✔ usage: an unknown verb or flag, and start or enable, exit 4 (310.397689ms)
|
||||
✔ without --send, render is the template with its placeholders filled, byte for byte (243.067925ms)
|
||||
✔ render --send with the preview set ends ExecStart with --send and changes nothing else (197.315207ms)
|
||||
✔ --send without the whole preview set is refused and writes nothing (786.337746ms)
|
||||
✔ install --send into --dir with --no-reload writes the sending unit, and status says whether it sends (438.522357ms)
|
||||
✔ uninstall and status take no --send (39.481397ms)
|
||||
✔ install refuses while the unit is running, in any state but inactive or failed, and changes nothing (246.234399ms)
|
||||
✔ when the state read fails, install and uninstall refuse with one line and change nothing (57.05951ms)
|
||||
✔ install needs --copy naming a copy that exists, and refuses outside the canonical checkout without --allow-worktree (48.869493ms)
|
||||
✔ install refuses unless the bus unit runs from the same copy, naming both paths, and changes nothing (215.918307ms)
|
||||
✔ render --copy runs the copy and reads the queue in the checkout; without --copy the unit is unchanged (18.00479ms)
|
||||
✔ the uid header takes only an Authentik uid; the username header still takes a username (286.34734ms)
|
||||
✔ a preview value cannot start with - or . (27.235136ms)
|
||||
✔ status names the copy and whether refactor has moved past it, and says when the unit runs from no copy (500.456892ms)
|
||||
✔ Settings form shows every scope and held role; gated save, exact payload, retained draft and no automatic retry (3663.758042ms)
|
||||
2026-10-15T22:40:08.703Z settings user="jason" changed
|
||||
✔ REQ-WEB-1 signed-in same-origin Settings write reaches exactly one human operation (39.568024ms)
|
||||
✔ REQ-WEB-1 Settings writes require enabled send, named Host, identity, exact Origin and same-origin Fetch Metadata (19.725482ms)
|
||||
✔ REQ-WEB-1 Settings validates JSON, size, revision and strict writable keys before broker call (13.965709ms)
|
||||
✔ tokens.css and icons.svg are byte copies of docs/design (1.92462ms)
|
||||
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.768695ms)
|
||||
✔ shipped strings are unchanged, and the web keeps its write boundary (3.622226ms)
|
||||
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8278.224864ms)
|
||||
✔ five states on Board, Inbox, Tasks, Agents and Trail (2268.122681ms)
|
||||
✔ Queue, Business and Settings in their five states, with no move button (5671.547571ms)
|
||||
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1130.402396ms)
|
||||
✔ the server log names no config path when the system config refuses (264.823611ms)
|
||||
ℹ tests 113
|
||||
ℹ suites 0
|
||||
ℹ pass 113
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 55533.835255
|
||||
Reference in New Issue
Block a user