docs: register Sage row 121 approval and row 49 review; add Rocko row 49 r1 packet

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-15 11:21:33 -05:00
co-authored by Claude Opus 5.5
parent 8a4c8fca67
commit ea5e70fa8f
73 changed files with 5572 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
# Row 49 / #1535 — bus comment cursor across restarts, round 1
Base: `7b166a0f1` (origin/refactor). Detached worktree:
`/mnt/storage/src/mosaic-stack-rocko-49`. Source code is uncommitted; Sage lands
`build.patch`. Reviewers: Darkwing and Filbert. Scope is comments only.
## Root cause and change
The adapter's comment count and last ID caches vanished at restart. Startup
reconcile seeded a new count without checking the comment list, and the next
updated-window poll could miss a comment posted during a long outage.
The broker now stores the highest observed comment ID per business/task in a
separate append-only table. Adapter startup restores IDs before reconciliation;
the first reconcile checks comment lists even if no count is cached. Cursor rows,
snapshots and comment events commit in one transaction. ID, expanded count and
updated caches advance after success. A cursor advances for bot comments too,
but only foreign comments contribute to external events. Project moves retain
the task ID. Neither task_current, task digests, event kinds nor tracker client
changes. recordTask's existing return shape remains unchanged.
## Exact persisted state and growth bound
`DDL.sql` contains the exact 3b→3c DDL: task_comment_cursors with `seq INTEGER
PRIMARY KEY AUTOINCREMENT`, `business TEXT`, positive `task_id` and positive
`last_comment`; an index on business/task_id/seq; three append-only guards and
one strictly-increasing-ID guard. No timestamp or count is stored per cursor.
An immutable meta entry `comment_cursor_epoch` is inserted once at creation or
migration; the existing version/digest metadata advances from 3b to 3c only in
the explicit offline migration.
A row is appended only when the maximum observed ID increases. Empty polls,
unchanged counts, restarts, already-seen IDs and comment deletions append none.
Growth is bounded by the number of observed new comments, usually one row per
batch, never by poll frequency. No pruning is introduced.
## Live-copy schema evidence and ordered migrations
Confirmed from CODE, never the live database, at live-copy commit
`2f57e3c5995dc1c75fb506b43bea602172908bb1`: Store requires version 3b,
and schema.sql SHA-256 is
`179ffe356d4ff19a49b5ebad39b6c6bfd7771deb8e1b7c55b5e746f039d69e65`.
`live-schema-evidence.txt` records the commands/results. The same immutable DDL
is pinned in src/migrations/3b.sql and independently in the test fixture.
The migration test creates a schema-current 3b fixture, closes it, copies it and
migrates only the copy. Source bytes and existing event evidence are preserved.
It checks ordinary startup refusal, explicit migration/reopen/idempotence,
writer-lock refusal, schema drift and incorrect metadata refusal. A retained
future3d fixture appends a named 3c→3d step and proves the actual engine executes
3b→3c→3d in order. Researcher's row 123 can append its step and leave prior SQL
pinned. Each step validates its input digest/version and commits its DDL, metadata
and optional initializer atomically, then the next step runs. A failed later step
can resume from the last committed version. Normal startup never migrates.
Migration DDL lives under src/migrations so stack-copy's existing bus/src allowlist
includes it; no stack-copy.sh edit.
`MIGRATION.md` describes Mos's separately approved downtime and re-pin window.
No seat reads or migrates the live store, or starts/stops a live unit.
## Initial cutoff and clock skew
Before any durable ID exists, timestamps are compared to database creation/
migration epoch minus a stated 60-second margin, rounded down to tracker seconds.
A test pins a comment 30 seconds behind host time. This favors a possible cutover
duplicate over losing a comment within the margin. Historical comments older
than that cutoff are baselined without an event. More than 60 seconds of tracker
lag or downtime before the initial migration cutoff remains outside this initial
guarantee; a clock ahead can replay already emitted comments once at migration.
After the first ID is durable, timestamps never determine freshness and long
outages do not reset the cursor.
## Approved scope extension — sage-0304, 2026-10-15 16:05:10Z
`packages/bus/src/store.mjs` exports SCHEMA_VERSION. `scripts/land-work/read.mjs`
imports it instead of its local 3b literal, and updates the explanatory comment.
Without this, its digest check would follow 3c but every version check would
refuse a valid database. No other land-work behavior changes. test-land-work.sh
is included in the builder gate; Darkwing (row 116 owner) reviews this row.
## Verification and honest corrections
Sage sage-0307 granted the slot. busy.py is chained with && and returns a failing
exit if busy. DOCKER_HOST=unix:///nonexistent.sock for all checks; no Docker run
is required by the targeted gate. Gate list: bus, tasks, cli, webui, harness and
seat node suites, plus scripts/test-land-work.sh. Relative import searches find
these Store/Broker/adapter consumers; test-land-work.sh is the shell reader of
Store/schema and land-work/read.mjs. Other shell scripts do not read the changed
paths; the brief's full shell gate remains Sage's landing gate.
Focused red logs show the missing restart behavior and migration before code.
An initial gate lacked the worktree's node_modules link; the CLI launcher refused
that setup. The link now points to canonical pinned dependencies, as prior seat
worktrees do. A second gate caught a candidate packaging defect: SQL first lived
outside bus/src and pinned copies omitted it. SQL now lives under src/migrations;
the real pinned-install test verifies it. Both failed runs are retained. Neither
was a known #258/#261/#262/#263 flake. Final counts/results follow below.
Final gate: seven steps green, zero failures/skips. Node counts: bus **178**,
tasks **97**, cli **195**, webui **112** (real browser tests), harness **84**,
seat **132**. land-work selftests: **84** passed, zero failed. Bus was rerun
after adding final migration metadata/command coverage. DOCKER_HOST remained
`unix:///nonexistent.sock`; no Docker run, network or daemon action occurred.
No known flake recurred. `gate/results.json` and per-suite logs are the evidence.
22/22 mutants killed; no survivor, timeout, syntax/import failure or invalid
mutation. `MUTANTS.md` maps every fault to its behavioral test. All 17 candidate
hashes were restored. A fresh detached reconstruction applied build.patch at
the base and verified all 17 manifest paths; the temporary verification worktree
was removed. The restored restart/skew file also passed 5/5 after the mutants.
The cwd/argv-scoped node/Chromium sweep found zero owned runners. No live
systemd, dataRoot, token, binding, Settings or bus endpoint was touched.
+18
View File
@@ -0,0 +1,18 @@
CREATE TABLE task_comment_cursors (
seq INTEGER PRIMARY KEY AUTOINCREMENT,
business TEXT NOT NULL,
task_id INTEGER NOT NULL CHECK (task_id > 0),
last_comment INTEGER NOT NULL CHECK (last_comment > 0)
) STRICT;
CREATE INDEX task_comment_cursors_latest ON task_comment_cursors (business, task_id, seq);
CREATE TRIGGER task_comment_cursors_no_update BEFORE UPDATE ON task_comment_cursors
BEGIN SELECT RAISE(ABORT, 'task_comment_cursors is append-only'); END;
CREATE TRIGGER task_comment_cursors_no_delete BEFORE DELETE ON task_comment_cursors
BEGIN SELECT RAISE(ABORT, 'task_comment_cursors is append-only'); END;
CREATE TRIGGER task_comment_cursors_no_replace BEFORE INSERT ON task_comment_cursors
WHEN EXISTS (SELECT 1 FROM task_comment_cursors WHERE seq = NEW.seq)
BEGIN SELECT RAISE(ABORT, 'task_comment_cursors is append-only'); END;
CREATE TRIGGER task_comment_cursors_forward BEFORE INSERT ON task_comment_cursors
WHEN NEW.last_comment <= COALESCE((SELECT last_comment FROM task_comment_cursors
WHERE business = NEW.business AND task_id = NEW.task_id ORDER BY seq DESC LIMIT 1), 0)
BEGIN SELECT RAISE(ABORT, 'comment cursor must advance'); END;
+15
View File
@@ -0,0 +1,15 @@
# Review handoff
- Issue #1535, row49, round1; Darkwing and Filbert.
- Claim rev1272. in-review rev1276; missing-token request outcome rev1277,
exit1. Sage posts queue review request as jarvis.
- Base7b166a0f1; 17 source paths. Candidate manifest SHA-256:
87ef1321a1a08be64a20499889fc24b5549b47d6d33abfd86db95f62fb3cb6d0.
- Source uncommitted in /mnt/storage/src/mosaic-stack-rocko-49.
- Own append-only stage logs committed by explicit pathspec as8a4c8fca6;
no push. Packet remains for Sage's scan/commit.
- Slot returned16:18:57UTC after all runners, restored hashes and empty sweep.
- BUILD.md and MIGRATION.md describe scope and human-only live boundaries;
DDL.sql names Researcher's 3c starting point. Keep the future3d chain test.
- packet-manifest.sha256 paths are relative to this packet directory;
candidate-manifest.sha256 paths are relative to repository root.
@@ -0,0 +1,46 @@
# Row 49: explicit 3b→3c migration and cutover
Development seats use fixture databases only. These instructions are a plan,
not live authorization. Mos runs the live operation only after their go, during
a re-pin window that includes bus downtime. Nobody reads the live store for
build/review evidence. There has been no live unit, file, bus or Console action.
1. Sage lands the reviewed candidate and identifies its pinned copy. Mos schedules
the downtime/re-pin window and stops the bus using the established window
procedure. No writer may remain; the migrator refuses an existing writer lock
and never removes a stale lock. Resolve any lock under the established human
recovery procedure, not by bypassing it.
2. Mos takes a consistent restorable backup of the stopped bus directory, including
SQLite WAL state if present. Do not copy only bus.sqlite while WAL data exists.
Record the backup identity and current copy, without printing database contents.
3. From the NEW pinned copy, Mos invokes the explicit migration, passing the
actual absolute dataRoot (not a database filename):
```sh
node /absolute/new-copy/packages/bus/src/migrate-schema.mjs /absolute/dataRoot
```
Run this promptly after stopping the old bus: the initial epoch is migration
host time minus a 60-second skew margin. Comments older than that initial cutoff
cannot reliably be distinguished from historical comments without prior IDs.
More than a minute of pre-migration downtime or tracker lag is outside this
initial guarantee; arrange the window accordingly. Once 3c has run, later
outages use persisted IDs and have no timestamp-window limit.
4. Migration validates source version, full trusted schema digest, metadata, WAL,
timestamps and integrity before applying each named step. 3b→3c adds only the
DDL in DDL.sql, creates the epoch and updates version/digest atomically. It
preserves all existing evidence. Already-current is a validated no-op. An
unknown version, damage, wrong metadata, missing store or active writer refuses.
Stop the window on refusal; do not bypass validation or silently start old code
against upgraded data. Future steps chain in order, with each committed version
an explicit resume boundary.
5. Mos completes the re-pin and starts the new bus in the same approved window.
Its startup reconcile restores cursor IDs and reads comments; the new copy is
required because an old 3b Store refuses 3c. Perform the agreed human acceptance
check. Any Console or other unit action remains a separately approved Mos step.
Rollback: code alone cannot downgrade 3c to 3b. With the bus stopped and Mos's go,
restore the complete consistent pre-migration bus-directory backup AND the prior
pinned copy. Restoring backup discards evidence recorded after the backup; Sage
and Mos must decide the recovery rather than silently doing so. No automatic
schema downgrade, pruning or live repair ships in this row.
+31
View File
@@ -0,0 +1,31 @@
# Acceptance mutants
22/22 killed; no timeout, syntax/import failure, invalid mutation or survivor.
Each runner owns a process group; a timeout kills the entire group and never counts as a kill.
SQL mutants update both new-schema and migration DDL, with the source checksum recomputed,
so they reach behavioral assertions rather than fail at schema import.
| Check / deliberate fault | Test | Result |
| --- | --- | --- |
| 01-restore-durable-ids | packages/tasks/tests/comment-restart.test.mjs | KILLED |
| 02-persist-observed-ids | packages/tasks/tests/comment-restart.test.mjs | KILLED |
| 03-startup-comment-read | packages/tasks/tests/comment-restart.test.mjs | KILLED |
| 04-exclude-bots | packages/tasks/tests/comment-restart.test.mjs | KILLED |
| 05-strict-new-id | packages/tasks/tests/comment-restart.test.mjs | KILLED |
| 06-cache-after-commit | packages/tasks/tests/comment-restart.test.mjs | KILLED |
| 07-event-cursor-atomicity | packages/tasks/tests/comment-restart.test.mjs | KILLED |
| 08-no-empty-poll-row | packages/tasks/tests/comment-restart.test.mjs | KILLED |
| 09-initial-clock-margin | packages/tasks/tests/comment-restart.test.mjs | KILLED |
| 10-durable-initial-epoch | packages/tasks/tests/comment-restart.test.mjs | KILLED |
| 11-validate-cursor-input | packages/bus/tests/comment-cursors.test.mjs | KILLED |
| 12-sync-only-cursor-write | packages/bus/tests/comment-cursors.test.mjs | KILLED |
| 13-business-isolation | packages/bus/tests/comment-cursors.test.mjs | KILLED |
| 14-explicit-offline-only | packages/bus/tests/comment-migration.test.mjs | KILLED |
| 15-pinned-source-metadata | packages/bus/tests/comment-migration.test.mjs | KILLED |
| 16-chain-all-steps | packages/bus/tests/comment-migration.test.mjs | KILLED |
| 17-preserve-source-copy | packages/bus/tests/comment-migration.test.mjs | KILLED |
| 18-land-work-version | scripts/test-land-work.sh | KILLED |
| 19-append-only-cursor-evidence | packages/bus/tests/comment-cursors.test.mjs | KILLED |
| 20-monotonic-cursor | packages/bus/tests/comment-cursors.test.mjs | KILLED |
| 21-exclusive-writer-lock | packages/bus/tests/comment-migration.test.mjs | KILLED |
| 22-migration-command | packages/bus/tests/comment-migration.test.mjs | KILLED |
+1
View File
@@ -0,0 +1 @@
7b166a0f196026b5896473e0e09181e996d962a9
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
CLEAR
@@ -0,0 +1 @@
CLEAR
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env python3
import pathlib,os,sys,json
busy=[]
for p in pathlib.Path('/proc').glob('[0-9]*'):
try:
cwd=os.readlink(p/'cwd')
if not cwd.startswith('/mnt/storage/src/mosaic-stack'):continue
args=[a.decode(errors='replace') for a in (p/'cmdline').read_bytes().split(b'\0') if a]
if not args:continue
exe=pathlib.Path(args[0]).name
node=exe=='node' and any(a=='--test' or a.startswith('--test-') or a.endswith('.test.mjs') for a in args[1:])
browser='chrom' in exe and any(a.startswith('--headless') for a in args[1:])
shell=exe in ['bash','sh'] and len(args)>1 and pathlib.Path(args[1]).name.startswith('test-')
if node or browser or shell:busy.append({'pid':int(p.name),'exe':exe,'cwd':cwd})
except OSError:pass
print('BUSY '+json.dumps(busy) if busy else 'CLEAR')
sys.exit(bool(busy))
@@ -0,0 +1,17 @@
90e27a81463069afcf628d39e320db803a49ee281448f138c5703a5df191eb7e packages/bus/README.md
7587ac1df813e5148fdb73dc4733ec8064e6a142e4647f0da71be009a73eebdb packages/bus/schema.sql
578d0c265c758655e506a0e2a66ca4aeb8337ff89e897fc79a7832b9fb81de40 packages/bus/src/broker.mjs
af5031e21c4fee8c2252a7d0b92cd8492cca2efdbcb6c7c47f1ffe069a933455 packages/bus/src/migrate-schema.mjs
b753f630301bf125e9e286b4156d0736930dd8fb9cd8eb2ebf316688f9191a07 packages/bus/src/migrations/3b-3c.sql
179ffe356d4ff19a49b5ebad39b6c6bfd7771deb8e1b7c55b5e746f039d69e65 packages/bus/src/migrations/3b.sql
208c8d6208e63b4b0110eca83b8a61d97735d13a539a33cae0afed2d0dab63be packages/bus/src/store.mjs
aa6544d51c7741b99f9115ba69390dc14b6b70e7978e144191f9eac55bb212de packages/bus/tests/comment-cursors.test.mjs
1f12df682689705223493c4153d327baf4499f8a457262bf897a759c4b434622 packages/bus/tests/comment-migration.test.mjs
179ffe356d4ff19a49b5ebad39b6c6bfd7771deb8e1b7c55b5e746f039d69e65 packages/bus/tests/fixtures/schema-3b.sql
0e079cf703233e70fd057d01d852140693e023a6e9a2cd35409b168c4d08c3ed packages/bus/tests/schema.test.mjs
7f16447f47bc10874df1b400dd85416ba9115b984222436666880f806610ea0e packages/tasks/README.md
4e263e5b73c95ece04397f70524067e63cf3ab9778341c1e18ebcb995851ee50 packages/tasks/src/adapter.mjs
39887268258c3b651246b3596a866005fc193cd8ee5f151a02492967069f2d06 packages/tasks/src/sync.mjs
c65abc7df9b6e1ad18e164fb4a4fe6127c3375a74079389859086088ee6223e7 packages/tasks/tests/comment-restart.test.mjs
477aef1cfafa35490b50568601b9ad0de35ff3789b2069526f6548b329e6f965 packages/tasks/tests/world.mjs
0e23a3dceac3a80dd5e2fa144ad96b12f8691d63a179920f9063d4c6a083984a scripts/land-work/read.mjs
@@ -0,0 +1,184 @@
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (14.204951ms)
✔ agents: working counts what the holder received and has not read; routed alone is not working (13.910211ms)
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (22.68387ms)
✔ agents: working wins over waiting, and Input needed still shows (9.575619ms)
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (14.874145ms)
✔ agents: state reads only its own business, even for a run name both use (11.10281ms)
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (12.317223ms)
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (11.392683ms)
✔ agents: stranded is oldest first and capped at 50 for a role (57.016538ms)
✔ agents: stranded is per role and per business (18.978326ms)
✔ agents: a message routed to a held role and not yet received is not stranded (7.093856ms)
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (16.444672ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (15.187757ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (16.098609ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (9.447922ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (9.620951ms)
✔ a recommendation that is not exactly one option key refuses invalid-recommendation after the options (16.611918ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (10.50657ms)
✔ task action subjects and linked decision trail are complete and ordered (16.347543ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (7.00047ms)
✔ business isolation includes inherited object names and cross-business message references (11.607591ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (15.740974ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (8.114717ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (11.945808ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (7.901772ms)
✔ both arbiters require human resolution when their cross-role route is themselves (13.89849ms)
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (13.87552ms)
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (1.158918ms)
✔ comment cursors isolate businesses, move only forward and remain append-only (9.942736ms)
✔ only trusted sync writes validated cursors; events and cursor share rollback (7.34407ms)
✔ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (36.103883ms)
✔ migration refuses active writers and drifted legacy schemas, with no partial upgrade (57.522291ms)
✔ named migration registry chains 3b through 3c to a fixture-only future version in order (37.774338ms)
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (10.220983ms)
✔ credentials refuses a role session and any argument (9.787797ms)
✔ credentials reads the status on each request, and a broker without one lists none (10.757991ms)
✔ the broker process answers credentials without a token, path or variable in its output, log or store (61.073581ms)
✔ only validated broker references load; returned data and exceptions cannot expose a known token (2.146996ms)
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (1.320734ms)
✔ expiry refuses use and env references never become client data (0.417679ms)
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (0.865039ms)
✔ opaque tokens shorter than 16 characters refuse before use (0.248738ms)
✔ a human resolve sends the raising role one message from the human, and its request is the answer (24.06906ms)
✔ a cross-role decision resolved by its arbiter comes from that role and run, with no request (13.187684ms)
✔ a human withdraw sends one message with the note (13.098121ms)
✔ a human seen before the close leaves the request on the close's answer, not the seen (16.151824ms)
✔ the raising run's withdraw, a supersede, a within-role raise and seen send nothing (15.978528ms)
✔ refused closes send nothing: invalid-choice, decision-closed, not-resolver (15.783336ms)
✔ a raising role nobody holds at the close gets the message on the next claim (13.830742ms)
✔ a human DECISION send keeps its own instruction as the request, not a close's answer (13.097903ms)
✔ roles lists open decisions routed to a role, and never the human inbox (17.926583ms)
✔ recent lists closed decisions of every route, newest close first, with how each closed (16.997133ms)
✔ recent gives the closing event as closed when the decision was seen first (17.180096ms)
✔ recent stops at RECENT rows (57.216438ms)
✔ every row is what trail already gives the same session for that id (24.542349ms)
✔ a role session can't list decisions, so it can't read another role's open decisions (23.981308ms)
✔ decisions is a read: one business, strict arguments, nothing written (23.80236ms)
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (13.213218ms)
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (7.639076ms)
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (8.481423ms)
✔ endLaunch leaves a claim another run took alone (9.191965ms)
✔ refuse records action.refused against the caller with the code only (6.92126ms)
✔ launches off refuses role.launch with launch-revoked until launches on (10.686819ms)
✔ broker process: launch ops authorize role.launch, record refusals and end runs (73.14704ms)
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (1.25466ms)
✔ process reader gets own kernel identity without exposing environment values (0.377922ms)
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.529354ms)
✔ real pid 1 remains inspectable when its environment is protected (0.207429ms)
✔ limits binds actor from human proof and rejects role, reader, tester and forged actor (15.486616ms)
✔ runtime wires verified human settings to the actual shared file writer and actor receipt (28.78493ms)
✔ uncertain organization receipts leave bus startup and reads alone but refuse writes until reconciled (22.278652ms)
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (16.047835ms)
✔ missing and foreign-business citations refuse and roll back message and grant (10.008354ms)
✔ cross-role sends still need a matching resolved decision and consume it once (14.69295ms)
✔ messages pages all human messages newest first without writes; read state is history (211.873021ms)
✔ messages refuses role callers, invalid paging and cursors outside this human feed (15.754103ms)
✔ views messages passes only its paging arguments through (0.201639ms)
✔ messages isolates businesses even with a valid foreign cursor (9.600953ms)
✔ the assistant sends to the user and to the chief of staff only (20.833556ms)
✔ an assistant send to any other role, or to the tester, is refused and logged (19.431127ms)
✔ only the user and the chief of staff may send to the assistant; the tester is refused by default (17.256432ms)
✔ a sender-side list alone refuses a send to a role that has no list (16.030851ms)
✔ a target-side list alone refuses a send from a role that has no list (17.243162ms)
✔ the sender list is checked before the target list (13.786601ms)
✔ roles without the field behave exactly as before (21.3031ms)
✔ a business with no role definitions at all is unlimited (15.688328ms)
✔ a malformed messaging field refuses at boot (7.233199ms)
✔ the assistant's review.request to a reviewer is refused and logged, even with an approved decision (17.616886ms)
✔ a review.verdict to the assistant from a role outside its receiveFrom is refused and logged (18.831077ms)
✔ review requests and verdicts between roles without the field are unchanged (15.129264ms)
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (61.121681ms)
✔ startup token refusal returns safe code without value or partial listening broker (47.265694ms)
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (57.812384ms)
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (87.916068ms)
✔ trusted host registers later launches; socket clients never have a registration verb (56.87281ms)
✔ runtime excludes declared project roots even when host supplies no repoRoots (41.974148ms)
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (54.539409ms)
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (23.543933ms)
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (10.97289ms)
✔ a message with no reply gives an empty list (8.735418ms)
✔ replies come oldest first and stop at REPLIES (47.470179ms)
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (17.133656ms)
✔ strict arguments; a role session gets human-required (21.546357ms)
✔ views passes replies through as one call with the id (0.173835ms)
✔ requirements returns the committed list to a holding role session, and no one else (17.027096ms)
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (8.590232ms)
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (9.170806ms)
✔ a push is recorded as the action.allowed for git.push.working on the task, and only the host records one (19.08643ms)
✔ review.request names the latest pushed commit and sends the reviewer a REVIEW-REQUEST (15.904564ms)
✔ review.request says which run holds the reviewer role, or null, and a null request waits for a claim (15.839849ms)
✔ review.request refuses a commit that isn't the latest push, a non-reviewer and a role without the action (21.141944ms)
✔ reviewFetch answers only the addressed reviewer, for the request it cites (14.097222ms)
✔ review.verdict answers one request once, to the role that asked (16.691564ms)
✔ task.close on a pushed task needs an approve verdict for the latest push (22.312721ms)
✔ a refusal the host decided is filed on the task it names, and only a task ref counts (12.578619ms)
✔ v3b prototype refusals, views and append-only mutations (52.286002ms)
✔ gated approval authorizes once, survives store reopen, and fresh approval works (20.630602ms)
✔ another run cannot consume an approval; a failed check leaves it usable (12.995201ms)
✔ two scheduled callers have exactly one grant and one consumed refusal (9.225849ms)
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (19.348092ms)
✔ class drift gated to cross-role refuses before consumption (11.933064ms)
✔ class drift cross-role to gated refuses before consumption (12.751145ms)
✔ class drift gated to within-role refuses before consumption (11.81697ms)
✔ class drift cross-role to within-role refuses before consumption (13.68665ms)
✔ class drift within-role to gated refuses before consumption (11.674539ms)
✔ class drift within-role to cross-role refuses before consumption (11.377197ms)
✔ message.send consumes approval and prevents a later send or authorize (14.081567ms)
✔ role.revoke consumes approval and prevents a later revoke or authorize (16.423868ms)
✔ a business that never switched launches reads allowed with no time and no event (14.945803ms)
✔ launches revoked and then restored read allowed, with the restore time and event id (9.937997ms)
✔ a task closed on the tracker leaves the open list, and total counts the open ones (10.732084ms)
✔ a tombstone is not open (8.656972ms)
✔ the open list stops at the cap and says so; total still counts every open task (11.668022ms)
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (8.281989ms)
✔ a human session reads the same state, with the same cap (10.13632ms)
✔ reading state writes nothing: every table keeps its row count (8.670922ms)
✔ creates private WAL store and excludes a second writer until explicit close (9.195052ms)
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (8.740087ms)
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (8.778653ms)
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (42.898119ms)
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (6.601363ms)
✔ async transactions refuse before invoking their function (4.779191ms)
✔ the session default fits under the reply cap and pages to the end with next (107.02939ms)
✔ the short form: no description, assignees by role, every listed key (67.516828ms)
✔ each filter, alone and together, and total counts every match (139.59457ms)
✔ task_ref reads one task in full, as the whole board has it (87.956498ms)
✔ no arguments is the read the Console and mosaic make, unchanged (66.800696ms)
✔ an unknown argument or a bad value refuses invalid-request (289.116786ms)
✔ tasks rows carry priority and requirement for every session that reads tasks (28.122158ms)
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (28.535065ms)
✔ an empty business and one with snapshots but no task.created read cleanly (6.838919ms)
✔ recordTask keeps sync reads and a role write apart (16.327699ms)
✔ read_at must be one canonical UTC format, so the projection compares strings safely (6.455356ms)
✔ a bad entry refuses the whole record (6.445869ms)
✔ taskView reads the projection for one business (7.840617ms)
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (15.493208ms)
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (280.319713ms)
✔ without an adapter the server refuses every task verb (14.688994ms)
✔ the runtime refuses an invalid adapter and closes a valid one (15.362832ms)
✔ the process loads the S3 adapter from plain-data trackers (113.35814ms)
✔ the tester binds only when the business sets bus.tester to exactly true (46.161488ms)
✔ a business may not name a role tester, so the tester address is never a role (13.433944ms)
✔ every verb outside the tester list refuses tester-refused and is recorded against the tester (30.732781ms)
✔ a tester message is from tester, starts [mos test], and writes no human.input or authority (12.856891ms)
✔ a role answers a tester only in reply to a tester message, and the answer never reaches the human (15.24297ms)
✔ the tester reads the human views a reader reads (16.461478ms)
✔ tester proof binds its own CLI, start time and nonce, refuses a managed run and allows an agent (1.03496ms)
✔ neither CLI's nonce proves the other: each proof is bound to its own CLI and nonce variable (0.348649ms)
✔ testerNonce reads only the tester's variable and gives null on any read error (0.183542ms)
✔ an envelope carries exactly one of cap, human and tester; no tester authenticator refuses (14.573729ms)
✔ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (603.639701ms)
✔ socket capability stamps launch identity; shared views use wire, no SQL client (18.46578ms)
✔ two wire claims serialize; a lost reply never automatically retries (12.957214ms)
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (7.379298ms)
✔ client preserves UTF-8 when a response divides a multibyte character (11.19722ms)
✔ committed mutation followed by dropped reply reports unknown and is never retried (9.09595ms)
ℹ tests 176
ℹ suites 0
ℹ pass 176
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 6127.343577
@@ -0,0 +1,241 @@
✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (1427.904586ms)
✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (63.82284ms)
✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (142.286234ms)
✔ NO_COLOR: no escape codes, except reverse video on the selected row (214.481669ms)
✔ 1-9 shows the decide flow's own lines and question; only y resolves (46.867159ms)
✔ the app shows the same decide text as mosaic decide (19.187482ms)
✔ any key but y or Y at the question answers No; nothing is sent (171.065509ms)
✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (59.625819ms)
✔ under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10) (234.691615ms)
✔ a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12) (30.979978ms)
✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (20.816461ms)
✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (390.722587ms)
✔ quitting at the question answers No and sends nothing, armed or not (31.955017ms)
✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (27.664863ms)
✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (43.413134ms)
✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (283.262311ms)
✔ at every size the decide box shows each line y depends on, or says it is hidden (8382.982447ms)
✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (63.34997ms)
✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (29.051797ms)
✔ help or a detail box cut to fit keeps its last line, Esc included (Dewey) (14.392159ms)
✔ keys: views, moving, trail of a selection, the command line, help and quit (73.013587ms)
✔ one option: the status line says Press 1, not 1-1 (15.554713ms)
✔ no business: the app says so and :business picks one (8.548785ms)
✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (1.847886ms)
✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (168.951774ms)
✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.412841ms)
✔ decide with --note still asks; a No sends nothing (9.381733ms)
✔ the app's business comes from a live bus host only (1.007561ms)
✔ 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.808205ms)
✔ draw is pure: same state, same frame (0.776433ms)
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (15.368975ms)
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (10.980225ms)
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (9.361021ms)
✔ decide prints a declining choice as declining (8.640638ms)
✔ an unknown outcome is reported once and never resent (7.300699ms)
✔ a decision closed before the answer arrives exits 2 and points at its trail (7.638996ms)
✔ a prefix that matches two open decisions exits 2 and resolves neither (7.257074ms)
✔ without --business a command uses the live host's business, and a stale host.json is not a host (6.771454ms)
✔ every human command refuses inside an agent run before it touches the bus (6.749563ms)
✔ usage errors exit 4; no business and no host is a usage error (7.415423ms)
✔ agents and tasks print through the broker (6.852527ms)
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.037559ms)
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (31.46294ms)
✔ trackers come from the tracker.* variables of the one project that names a tracker project (51.232562ms)
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (26.88494ms)
✔ two projects that each name a tracker project refuse, since the boot shape holds one (25.74653ms)
✔ a business without tracker.baseUrl gets no trackers entry (26.061158ms)
✔ an unknown business and a broken system config refuse with exit 3 (46.510296ms)
✔ follow prints an unsolicited message after plain talk returned (25.25532ms)
✔ talk --follow continues after reply and prints a later unsolicited message (22.654466ms)
✔ inbox lists all unread human messages under decisions, does not deliver or read them (196.346379ms)
✔ follow refuses agent context and bad arguments (7.661422ms)
✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (8.078722ms)
✔ plain talk leaves the process SIGINT handlers unchanged (9.336137ms)
✔ empty views say so (0.56444ms)
✔ the trail keeps the broker's order and names a decision's task without its rows (0.69533ms)
✔ tasks print the tracker fields the snapshot carries (0.119003ms)
✔ the review loop: the coder pushes and asks, the reviewer fetches and approves, and the task's trail holds each step (276.206822ms)
✔ refusals: no action, non-fast-forward, an unknown commit or request, and a push for another task moves only that task's ref (269.370279ms)
✔ the size cap refuses a push before any byte (134.996702ms)
✔ a fetch over the cap refuses before any byte reaches the reviewer (281.913854ms)
✔ the socket refuses a request with no capability, an unknown one, and a bad request line (136.70351ms)
✔ a rigged clone runs nothing: hooks, fsmonitor, a filter, a gitlink and a symlinked .git/config leave no marker (226.057686ms)
✔ an identity with no role, or of another business, refuses not-a-role and moves no ref (170.244445ms)
✔ a review of a project the business no longer has refuses unknown-project (167.151988ms)
✔ past maxActive connections the socket refuses git-busy, and a hung-up connection frees its slot (120.910174ms)
✔ close() answers host-closing to a connection still sending its request line (116.570243ms)
✔ a bundle that trickles in refuses body-timeout at its deadline and frees its slot (525.624754ms)
✔ a reviewer gone before its bundle was sent is filed fetch-aborted, not as the host's fault (387.380247ms)
✔ a reviewer that hangs up the moment it has the whole bundle was served, not aborted (431.237815ms)
✔ send: a close after every byte left is served; one that fails a queued write is fetch-aborted (42.302371ms)
✔ a bundle with no bytes for bodyIdle refuses body-timeout long before its deadline (431.049466ms)
✔ send: a close once the loop is past its last chunk is served, and a failed write there is fetch-aborted (43.884849ms)
✔ letGo waits for an answer to leave for at most its cap, then destroys the socket (302.357819ms)
✔ a record that fails and can't be undone refuses rollback-failed, which the host logs (293.851268ms)
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (812.147408ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (133.342112ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (76.35783ms)
✔ two bus starts racing for one data root leave one host running; the other refuses that a host already runs (444.988694ms)
✔ a host takes over a host.lock its dead host left, and puts back a live claim that replaced it meanwhile (1.696083ms)
✔ a host.lock that goes between the check and the read is tried again; one that can't be parsed still refuses as unreadable (0.709436ms)
✔ a live host's lock stays, however old the file is (0.494196ms)
✔ a host.lock left by a host killed with SIGKILL doesn't stop the next bus start (650.772352ms)
✔ while a host boots, a reader sees no host: the human commands still ask for --business, stop and status find none (181.374213ms)
✔ a host started before host.lock existed, holding only host.json, still refuses a second start, which leaves no lock (25.820447ms)
✔ a broker fork that throws leaves no host.lock and no host.json (27.634714ms)
✔ a broker send that throws leaves no host.lock and no host.json (30.893575ms)
✔ a broker that misses its boot wait leaves no host.lock and no host.json (28.848413ms)
✔ a notifier fork that throws stops the broker and leaves no host.lock and no host.json (83.598678ms)
✔ a notifier send that throws stops the broker and leaves no host.lock and no host.json (79.819097ms)
✔ 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 (86.169548ms)
✔ 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 (84.299012ms)
✔ a host.json that can't be written stops both children and leaves no host.lock (210.384058ms)
✔ an error event on the broker before it replies fails the start at once, and leaves no host.lock and no live child (29.597773ms)
✔ an error event on the notifier before it replies fails the start at once, and leaves no host.lock and no live child (78.402838ms)
✔ an error event on the broker while the notifier starts fails the start once the notifier replies (182.162401ms)
✔ 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 (183.628133ms)
✔ 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 (169.177027ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1081.943402ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (155.429645ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (78.930311ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (120.660528ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (123.0613ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (77.500211ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (129.922183ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (78.443165ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (137.774455ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (21.594065ms)
✔ bus stop refuses to signal a live pid that is not a bus host (203.261549ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (237.175197ms)
✔ bus start refuses with exit 3 without a notifier config (96.473866ms)
✔ bus start runs until bus stop; status reports it while it runs (626.931946ms)
✔ bus-service.sh renders the unit and installs it into a given directory (55.148632ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (66.564241ms)
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (12.495032ms)
✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (8.033054ms)
✔ each launch rereads limits and model selections; lowered limits keep sessions and log exact hit (18.545047ms)
✔ bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest (1176.358192ms)
✔ the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code (406.068339ms)
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (3505.1885ms)
✔ a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left (113.919203ms)
✔ a runner that stops at once ends its launch with the runner's reason (190.743436ms)
✔ a Claude Code launch with no session credential refuses before it starts (92.04268ms)
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (171.850663ms)
✔ a runner killed from the host ends its launch as killed, not failed (235.351822ms)
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (97.289639ms)
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (986.319428ms)
✔ 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 (597.06317ms)
✔ 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 (3605.916527ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (85.345697ms)
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (86.867186ms)
✔ a launch client that never closes its side doesn't hold the host's close (90.149191ms)
✔ a runner that ignores SIGTERM is killed when the host closes (1210.51669ms)
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (4.208477ms)
✔ migration needs no bus/config, prints whole converted document, is idempotent and never writes (8.759466ms)
✔ limits view/set use the human bus transport and pass only the explicit patch (1.031242ms)
✔ post-119 example and Jason fixture migrate with checked inherited models; wrong or absent config refuses (158.44061ms)
✔ zoned uses the IANA zone across DST (11.561521ms)
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (15.27872ms)
✔ two blocking decisions get two DMs with different nonces (9.969303ms)
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.178198ms)
✔ a failed DM is journaled, backs off, and is retried until it lands (10.415377ms)
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (11.499671ms)
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (12.396482ms)
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (9.210611ms)
✔ 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 (66.587499ms)
✔ a restart after the second refusal does not send before that refusal's 30 min are up (34.443171ms)
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (11.4283ms)
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (7.53495ms)
✔ an inbox read failure is logged and the next poll retries (0.612829ms)
✔ no Discord id reaches the journal or the log (7.948974ms)
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (1.716435ms)
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (1.881929ms)
✔ the journal: a whole file that is one torn line truncates to empty (1.155235ms)
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.414853ms)
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.822258ms)
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.859145ms)
✔ the journal: a symlinked directory refuses and says it is a link (0.296921ms)
✔ 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.434939ms)
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.371102ms)
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.424287ms)
✔ digest content stays within Discord's 2000 characters (0.254051ms)
✔ runLoop never overlaps ticks and stops after the one in flight (110.781103ms)
✔ a human command refused with human-required prints the other-tree line and exits 3 (1.806267ms)
✔ a --tester command refused with tester-required prints the other-tree line and exits 3 (0.648711ms)
✔ other exit-3 refusals print no other-tree line (0.961234ms)
✔ the line names no path (0.082183ms)
✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (353.477728ms)
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (374.738022ms)
✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (371.839233ms)
✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (243.708858ms)
✖ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (2335.309909ms)
✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (422.506345ms)
✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (145.066595ms)
✔ install never shows a template name on systemd 261; template drop-ins still refuse (153.320303ms)
✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (146.979151ms)
✔ inherited mosaic-.service.d override refuses without changing the unit (157.568628ms)
✔ inherited service.d override refuses without changing the unit (159.31668ms)
✔ inherited drop-in unreadable directory or file refuses before writing (137.271235ms)
✔ talk --tester sends a [mos test] message as tester from inside an agent and prints the role's reply (26.785834ms)
✔ talk --tester refuses text without the [mos test] prefix before any call (7.494529ms)
✔ the read verbs take --tester inside an agent, and the trail shows the tester as the sender (11.249612ms)
✔ decide, stop and launches take no --tester (8.282781ms)
✔ the tester transport runs tester-cli.mjs, and the tester's refusals exit 3 (0.308455ms)
✔ with no stand-in transport, --tester runs tester-cli.mjs on the data root's socket (0.439194ms)
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 (242.884135ms)
✔ the transport writes {business, verb, args} to the child and reads its JSON (29.833127ms)
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2123.384644ms)
✔ busExit and refuseInsideAgent (0.477555ms)
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (40.875699ms)
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1027.385396ms)
✔ a late reply the Console has listed still reaches the next talk (2027.788358ms)
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (10.456277ms)
✔ launches off and on go to the broker and change the business's launch state (8.428599ms)
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (8.087903ms)
ℹ tests 195
ℹ suites 0
ℹ pass 194
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 48729.492384
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:101:1
✖ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (2335.309909ms)
AssertionError [ERR_ASSERTION]: mosaic-bus: warning: business acme sets tracker.baseUrl, but no project file sets tracker.project; the broker gets no task verbs
node:fs:621
return binding.readFileUtf8(path, stringToFlags(options.flag));
^
Error: ENOENT: no such file or directory, open '/mnt/storage/scratch/tmp/stack-copy-aJ334I/copies/6575f99b4a4a09b0fc8dc01398a88333bcae6ca6/packages/bus/migrations/3b.sql'
at readFileSync (node:fs:621:20)
at file:///mnt/storage/scratch/tmp/stack-copy-aJ334I/copies/6575f99b4a4a09b0fc8dc01398a88333bcae6ca6/packages/bus/src/store.mjs:19:19
at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async node:internal/modules/esm/loader:650:26
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/mnt/storage/scratch/tmp/stack-copy-aJ334I/copies/6575f99b4a4a09b0fc8dc01398a88333bcae6ca6/packages/bus/migrations/3b.sql'
}
Node.js v26.8.1
mosaic: broker exited (1) before it replied
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/tests/stack-copy.test.mjs:111:84)
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: '',
expected: /bus host up/,
operator: 'match',
diff: 'simple'
}
@@ -0,0 +1,105 @@
✔ the boot config is checked before anything starts (12.221505ms)
✔ a business with no tracker entry refuses task verbs (8.967263ms)
✔ credential.expiring and .expired are recorded once per instance (20.433012ms)
✔ a token file that changes on disk records credential.changed (8.940243ms)
✔ autostart polls, reconciles and retries a startup the tracker was down for (31.924571ms)
✔ a refusal a restart must clear is not retried by the poll (6.835335ms)
✔ a poll that fires while two are queued is dropped (9.018359ms)
✔ close waits for a running verb and refuses one that has not started (36.647349ms)
✔ stopped bus resumes an empty task cursor after a long outage, exactly once (29.810424ms)
✔ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (22.960972ms)
✔ commit followed by crash before cache advance neither loses nor duplicates a comment (14.029927ms)
✔ failed event insert rolls back cursor and a retry still emits the comment once (12.089284ms)
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (10.13875ms)
✔ the bundled Vikunja is the pinned upstream image the runbook names (0.677045ms)
✔ every published port is on 127.0.0.1, and no secret is in the file (0.261543ms)
✔ the committed table loads frozen; its verbs are the broker task verbs (1.155586ms)
✔ a write names a verb the table has as its own key, not one it inherits (0.861613ms)
✔ the schema refuses any other shape (1.899372ms)
✔ the Field table in the slice 1 plan names the verbs each field is written by (0.512601ms)
✔ each verb refuses, asks for a decision and writes as the table and the role files say (144.429003ms)
✔ the list holds the PRD's requirement ids, in its order, at its version (1.142054ms)
✔ the committed list loads frozen (0.518629ms)
✔ the schema refuses any other shape (2.042976ms)
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (85.481066ms)
✔ the project list holds recorded project objects in the recorded list envelope (6.767417ms)
✔ the recorded task bodies pass the checks S3 applies to every read (0.424812ms)
✔ the client works against the fake over real HTTP with the platform fetch (87.974094ms)
✔ a correct install starts, and the first reconcile records tasks that already exist (21.653994ms)
✔ verbs refuse while a business is starting and after startup refused it (11.597711ms)
✔ startup refuses a token that can do more than its role needs (26.289898ms)
✔ startup refuses an unsupported version and flags an untested one (20.072499ms)
✔ startup refuses a board that the runbook did not install (24.261388ms)
✔ startup refuses a project the sync bot cannot read (5.849053ms)
✔ startup refuses a configured label the pm bot cannot see (6.655776ms)
✔ startup refuses an expired credential and a missing sync credential (12.40412ms)
✔ an unreachable tracker refuses with tracker-unavailable (5.563292ms)
✔ an edit in the UI is recorded once, with the fields that changed (69.94148ms)
✔ a move between open buckets is seen on the board, though updated does not change (35.909321ms)
✔ a person's comment is counted and a bot's is not (62.853184ms)
✔ the hourly reconcile catches a comment through comment_count (76.102533ms)
✔ a task closed in the UI leaves the open view with its done bucket (113.073443ms)
✔ a task that leaves the board is recorded as deleted, moved or out of reach (64.987547ms)
✔ a poll that read before a verb wrote does not overwrite the verb (34.105733ms)
✔ a tracker fault during a tick is reported and the next tick catches up (35.02089ms)
✔ a malformed answer refuses the tick with tracker-shape (12.486665ms)
✔ no token value reaches the database, the log or a refusal (44.974769ms)
✔ the first look at a task counts only comments inside the window (34.059209ms)
✔ with descendants off the adapter reads no project list and no child, as in v0 (40.370536ms)
✔ a three-level tree is read per project, and a project outside it is not (17.94978ms)
✔ a watch-only child keeps the business ready, and a failed child read refuses as transient (18.32525ms)
✔ a root without the installed buckets refuses with descendants on (6.587103ms)
✔ the project list refuses without read_all, and skips archived projects and saved filters (14.844219ms)
✔ descendants must be true or false (5.93382ms)
✔ a move inside the tree is a moved tombstone plus a snapshot under the new ref (57.252789ms)
✔ a child made after startup joins the tree at the next reconcile (53.140971ms)
✔ a child that leaves the tree has its tasks recorded missing, and the ledger keeps their history (57.885662ms)
✔ a child deleted or hidden between reconciles drops out at the next tick, and the root still refuses (80.947918ms)
✔ a reconcile that fails keeps the previous tree (32.927837ms)
✔ a closed task the cursor missed takes its own project's done bucket (33.615038ms)
✔ closed tasks read at startup take their own project's done bucket (9.341405ms)
✔ a closed task the cursor returns takes its own project's done bucket (34.752774ms)
✔ a task moved out of a child that leaves the tree at the same time is moved, not no-access (30.834171ms)
✔ a task in a bucket outside the five keeps its raw bucket id (29.656102ms)
✔ bucket 0 on a watch-only task never becomes a bucket or a state (76.741968ms)
✔ a child whose kanban view is deleted still refuses the tick, and the reconcile reads it watch-only (55.748787ms)
✔ a child dropped at a tick comes back at the next reconcile once the bot reads it again (34.979983ms)
✔ task.create needs a recorded human request and a requirement id (34.297269ms)
✔ task.create takes only an approved requirement id (19.564207ms)
✔ task.create cites the request a received message carries, not the message's id (16.250892ms)
✔ only labels named in the business file can be written (21.137117ms)
✔ task.schedule sets and clears a due date and relations (28.005657ms)
✔ assign and reassign move the role bots and record task.assigned (51.994457ms)
✔ task.update.assigned is for the assignee and records task.state (46.675677ms)
✔ a wrong expected digest records task.conflict and writes nothing (17.064885ms)
✔ a cross-role verb needs a resolved decision, used once (22.344587ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (24.522689ms)
✔ a task with pushed work closes only citing an approve verdict for its latest push, and a refusal writes nothing (31.878869ms)
✔ a lost answer is settled by a re-read and never retried (27.721746ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (37.893596ms)
✔ a task the sync bot cannot read refuses and records nothing (15.021889ms)
✔ verbs and polls for one business run one at a time (86.732867ms)
✔ a due date with milliseconds is written to the second (63.735305ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (39.704995ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (17.067921ms)
✔ a create whose final read fails succeeds and records task.created (33.621215ms)
✔ an edit between the last write and the final read shows as external on the next poll (37.290514ms)
✔ task.created is recorded when a later label write fails (14.19622ms)
✔ every verb acts in an installed child, on the child's own board (57.975653ms)
✔ task.create refuses a project outside the tree, a watch-only one and one that is not an id (27.413353ms)
✔ with descendants off, task.create takes only the root project (14.932514ms)
✔ a verb on a watch-only task, or a relation to one, refuses with project-not-installed (19.264887ms)
✔ a related task is read before authority: in another project than its ref names, task-moved; missing, task-not-found (26.325048ms)
✔ a task moved to another project of the tree refuses its old ref with task-moved and takes its new one (18.833608ms)
✔ a verb and a create in the child record the final read of the child's board (17.350349ms)
✔ a create in the child whose final read fails records the child's todo bucket (36.569519ms)
✔ a lost answer in the child is settled by a re-read of the child's board (22.820036ms)
✔ every related task is read before authority, not only the first (15.502746ms)
ℹ tests 97
ℹ suites 0
ℹ pass 97
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3508.520884
@@ -0,0 +1,87 @@
[
{
"suite": "pkg-bus",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/bus/tests/agents.test.mjs",
"packages/bus/tests/broker.test.mjs",
"packages/bus/tests/business.test.mjs",
"packages/bus/tests/comment-cursors.test.mjs",
"packages/bus/tests/comment-migration.test.mjs",
"packages/bus/tests/credential-status.test.mjs",
"packages/bus/tests/credentials.test.mjs",
"packages/bus/tests/decision-close.test.mjs",
"packages/bus/tests/decisions.test.mjs",
"packages/bus/tests/end-launch.test.mjs",
"packages/bus/tests/human.test.mjs",
"packages/bus/tests/limits.test.mjs",
"packages/bus/tests/message-citation.test.mjs",
"packages/bus/tests/messages.test.mjs",
"packages/bus/tests/messaging.test.mjs",
"packages/bus/tests/process.test.mjs",
"packages/bus/tests/replies.test.mjs",
"packages/bus/tests/requirements.test.mjs",
"packages/bus/tests/review.test.mjs",
"packages/bus/tests/schema.test.mjs",
"packages/bus/tests/single-use.test.mjs",
"packages/bus/tests/state.test.mjs",
"packages/bus/tests/store.test.mjs",
"packages/bus/tests/task-page.test.mjs",
"packages/bus/tests/task-read.test.mjs",
"packages/bus/tests/tasks.test.mjs",
"packages/bus/tests/tester.test.mjs",
"packages/bus/tests/transport.test.mjs"
]
},
{
"suite": "pkg-tasks",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/tasks/tests/adapter.test.mjs",
"packages/tasks/tests/comment-restart.test.mjs",
"packages/tasks/tests/deploy.test.mjs",
"packages/tasks/tests/fields.test.mjs",
"packages/tasks/tests/requirements.test.mjs",
"packages/tasks/tests/shapes.test.mjs",
"packages/tasks/tests/startup.test.mjs",
"packages/tasks/tests/sync.test.mjs",
"packages/tasks/tests/tree.test.mjs",
"packages/tasks/tests/verbs.test.mjs"
]
},
{
"suite": "pkg-cli",
"exit": 1,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/cli/tests/app.test.mjs",
"packages/cli/tests/cli.test.mjs",
"packages/cli/tests/config.test.mjs",
"packages/cli/tests/follow.test.mjs",
"packages/cli/tests/format.test.mjs",
"packages/cli/tests/git-socket.test.mjs",
"packages/cli/tests/host.test.mjs",
"packages/cli/tests/launch-limits.test.mjs",
"packages/cli/tests/launcher.test.mjs",
"packages/cli/tests/limits.test.mjs",
"packages/cli/tests/notifier.test.mjs",
"packages/cli/tests/other-tree.test.mjs",
"packages/cli/tests/stack-copy.test.mjs",
"packages/cli/tests/tester.test.mjs",
"packages/cli/tests/trackers-boot.test.mjs",
"packages/cli/tests/transport.test.mjs",
"packages/cli/tests/verbs.test.mjs"
]
}
]
@@ -0,0 +1,184 @@
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (14.694119ms)
✔ agents: working counts what the holder received and has not read; routed alone is not working (12.89286ms)
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (20.424092ms)
✔ agents: working wins over waiting, and Input needed still shows (9.132402ms)
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (11.873927ms)
✔ agents: state reads only its own business, even for a run name both use (9.992559ms)
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (11.295312ms)
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (10.693113ms)
✔ agents: stranded is oldest first and capped at 50 for a role (56.043313ms)
✔ agents: stranded is per role and per business (20.345092ms)
✔ agents: a message routed to a held role and not yet received is not stranded (7.070457ms)
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (14.224673ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (16.489082ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (14.270655ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (8.710815ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (8.863982ms)
✔ a recommendation that is not exactly one option key refuses invalid-recommendation after the options (13.794059ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (9.316961ms)
✔ task action subjects and linked decision trail are complete and ordered (16.222624ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (7.916002ms)
✔ business isolation includes inherited object names and cross-business message references (11.696746ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (15.886532ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (8.649519ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (12.971752ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (8.549761ms)
✔ both arbiters require human resolution when their cross-role route is themselves (15.07381ms)
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (14.44278ms)
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (1.75302ms)
✔ comment cursors isolate businesses, move only forward and remain append-only (10.960712ms)
✔ only trusted sync writes validated cursors; events and cursor share rollback (6.952661ms)
✔ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (39.574068ms)
✔ migration refuses active writers and drifted legacy schemas, with no partial upgrade (62.383266ms)
✔ named migration registry chains 3b through 3c to a fixture-only future version in order (38.700556ms)
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (10.638403ms)
✔ credentials refuses a role session and any argument (10.653125ms)
✔ credentials reads the status on each request, and a broker without one lists none (12.093482ms)
✔ the broker process answers credentials without a token, path or variable in its output, log or store (62.511757ms)
✔ only validated broker references load; returned data and exceptions cannot expose a known token (2.124537ms)
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (1.285414ms)
✔ expiry refuses use and env references never become client data (0.409731ms)
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (0.85467ms)
✔ opaque tokens shorter than 16 characters refuse before use (0.270753ms)
✔ a human resolve sends the raising role one message from the human, and its request is the answer (24.376591ms)
✔ a cross-role decision resolved by its arbiter comes from that role and run, with no request (13.51917ms)
✔ a human withdraw sends one message with the note (12.688532ms)
✔ a human seen before the close leaves the request on the close's answer, not the seen (16.269106ms)
✔ the raising run's withdraw, a supersede, a within-role raise and seen send nothing (17.222917ms)
✔ refused closes send nothing: invalid-choice, decision-closed, not-resolver (16.841133ms)
✔ a raising role nobody holds at the close gets the message on the next claim (14.147646ms)
✔ a human DECISION send keeps its own instruction as the request, not a close's answer (12.849278ms)
✔ roles lists open decisions routed to a role, and never the human inbox (16.630987ms)
✔ recent lists closed decisions of every route, newest close first, with how each closed (17.262893ms)
✔ recent gives the closing event as closed when the decision was seen first (17.263886ms)
✔ recent stops at RECENT rows (56.30294ms)
✔ every row is what trail already gives the same session for that id (24.95038ms)
✔ a role session can't list decisions, so it can't read another role's open decisions (25.964446ms)
✔ decisions is a read: one business, strict arguments, nothing written (24.080277ms)
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (11.704294ms)
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (7.712519ms)
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (8.94932ms)
✔ endLaunch leaves a claim another run took alone (9.078793ms)
✔ refuse records action.refused against the caller with the code only (7.810934ms)
✔ launches off refuses role.launch with launch-revoked until launches on (14.114655ms)
✔ broker process: launch ops authorize role.launch, record refusals and end runs (74.818856ms)
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (1.976245ms)
✔ process reader gets own kernel identity without exposing environment values (0.400604ms)
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.54122ms)
✔ real pid 1 remains inspectable when its environment is protected (0.217168ms)
✔ limits binds actor from human proof and rejects role, reader, tester and forged actor (14.086882ms)
✔ runtime wires verified human settings to the actual shared file writer and actor receipt (28.517428ms)
✔ uncertain organization receipts leave bus startup and reads alone but refuse writes until reconciled (21.114715ms)
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (16.366936ms)
✔ missing and foreign-business citations refuse and roll back message and grant (10.602507ms)
✔ cross-role sends still need a matching resolved decision and consume it once (14.280762ms)
✔ messages pages all human messages newest first without writes; read state is history (212.824651ms)
✔ messages refuses role callers, invalid paging and cursors outside this human feed (17.488634ms)
✔ views messages passes only its paging arguments through (0.218161ms)
✔ messages isolates businesses even with a valid foreign cursor (10.881854ms)
✔ the assistant sends to the user and to the chief of staff only (23.293826ms)
✔ an assistant send to any other role, or to the tester, is refused and logged (19.166302ms)
✔ only the user and the chief of staff may send to the assistant; the tester is refused by default (20.14079ms)
✔ a sender-side list alone refuses a send to a role that has no list (17.488013ms)
✔ a target-side list alone refuses a send from a role that has no list (19.472114ms)
✔ the sender list is checked before the target list (15.345074ms)
✔ roles without the field behave exactly as before (23.02958ms)
✔ a business with no role definitions at all is unlimited (16.800626ms)
✔ a malformed messaging field refuses at boot (7.129303ms)
✔ the assistant's review.request to a reviewer is refused and logged, even with an approved decision (19.048188ms)
✔ a review.verdict to the assistant from a role outside its receiveFrom is refused and logged (21.728873ms)
✔ review requests and verdicts between roles without the field are unchanged (17.133732ms)
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (60.584579ms)
✔ startup token refusal returns safe code without value or partial listening broker (39.068361ms)
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (53.156504ms)
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (95.132305ms)
✔ trusted host registers later launches; socket clients never have a registration verb (53.029448ms)
✔ runtime excludes declared project roots even when host supplies no repoRoots (38.518948ms)
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (55.638703ms)
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (24.05427ms)
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (11.790717ms)
✔ a message with no reply gives an empty list (9.749848ms)
✔ replies come oldest first and stop at REPLIES (49.020081ms)
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (17.242349ms)
✔ strict arguments; a role session gets human-required (20.040422ms)
✔ views passes replies through as one call with the id (0.17781ms)
✔ requirements returns the committed list to a holding role session, and no one else (17.088859ms)
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (9.274192ms)
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (9.346127ms)
✔ a push is recorded as the action.allowed for git.push.working on the task, and only the host records one (18.541174ms)
✔ review.request names the latest pushed commit and sends the reviewer a REVIEW-REQUEST (13.683565ms)
✔ review.request says which run holds the reviewer role, or null, and a null request waits for a claim (15.481336ms)
✔ review.request refuses a commit that isn't the latest push, a non-reviewer and a role without the action (21.449758ms)
✔ reviewFetch answers only the addressed reviewer, for the request it cites (14.20091ms)
✔ review.verdict answers one request once, to the role that asked (17.164639ms)
✔ task.close on a pushed task needs an approve verdict for the latest push (21.917811ms)
✔ a refusal the host decided is filed on the task it names, and only a task ref counts (12.828466ms)
✔ v3b prototype refusals, views and append-only mutations (52.124371ms)
✔ gated approval authorizes once, survives store reopen, and fresh approval works (20.392001ms)
✔ another run cannot consume an approval; a failed check leaves it usable (12.394165ms)
✔ two scheduled callers have exactly one grant and one consumed refusal (8.925564ms)
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (18.027903ms)
✔ class drift gated to cross-role refuses before consumption (12.497191ms)
✔ class drift cross-role to gated refuses before consumption (13.265983ms)
✔ class drift gated to within-role refuses before consumption (11.931205ms)
✔ class drift cross-role to within-role refuses before consumption (13.438046ms)
✔ class drift within-role to gated refuses before consumption (11.567283ms)
✔ class drift within-role to cross-role refuses before consumption (11.476235ms)
✔ message.send consumes approval and prevents a later send or authorize (13.519676ms)
✔ role.revoke consumes approval and prevents a later revoke or authorize (14.981673ms)
✔ a business that never switched launches reads allowed with no time and no event (11.338474ms)
✔ launches revoked and then restored read allowed, with the restore time and event id (9.122669ms)
✔ a task closed on the tracker leaves the open list, and total counts the open ones (9.13099ms)
✔ a tombstone is not open (7.372573ms)
✔ the open list stops at the cap and says so; total still counts every open task (10.999341ms)
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (7.858681ms)
✔ a human session reads the same state, with the same cap (9.651601ms)
✔ reading state writes nothing: every table keeps its row count (8.325051ms)
✔ creates private WAL store and excludes a second writer until explicit close (9.458006ms)
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (8.43887ms)
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (8.189648ms)
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (41.654297ms)
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (5.904631ms)
✔ async transactions refuse before invoking their function (4.591316ms)
✔ the session default fits under the reply cap and pages to the end with next (105.127339ms)
✔ the short form: no description, assignees by role, every listed key (68.356902ms)
✔ each filter, alone and together, and total counts every match (139.12956ms)
✔ task_ref reads one task in full, as the whole board has it (87.937707ms)
✔ no arguments is the read the Console and mosaic make, unchanged (66.361452ms)
✔ an unknown argument or a bad value refuses invalid-request (290.162112ms)
✔ tasks rows carry priority and requirement for every session that reads tasks (23.636732ms)
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (26.800046ms)
✔ an empty business and one with snapshots but no task.created read cleanly (6.38123ms)
✔ recordTask keeps sync reads and a role write apart (15.963ms)
✔ read_at must be one canonical UTC format, so the projection compares strings safely (5.704743ms)
✔ a bad entry refuses the whole record (5.600562ms)
✔ taskView reads the projection for one business (6.983907ms)
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (13.943232ms)
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (283.245328ms)
✔ without an adapter the server refuses every task verb (16.295978ms)
✔ the runtime refuses an invalid adapter and closes a valid one (15.168253ms)
✔ the process loads the S3 adapter from plain-data trackers (106.696101ms)
✔ the tester binds only when the business sets bus.tester to exactly true (46.368131ms)
✔ a business may not name a role tester, so the tester address is never a role (12.715382ms)
✔ every verb outside the tester list refuses tester-refused and is recorded against the tester (30.073718ms)
✔ a tester message is from tester, starts [mos test], and writes no human.input or authority (13.748584ms)
✔ a role answers a tester only in reply to a tester message, and the answer never reaches the human (16.067521ms)
✔ the tester reads the human views a reader reads (16.817791ms)
✔ tester proof binds its own CLI, start time and nonce, refuses a managed run and allows an agent (1.075242ms)
✔ neither CLI's nonce proves the other: each proof is bound to its own CLI and nonce variable (0.334655ms)
✔ testerNonce reads only the tester's variable and gives null on any read error (0.19747ms)
✔ an envelope carries exactly one of cap, human and tester; no tester authenticator refuses (14.562903ms)
✔ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (598.045324ms)
✔ socket capability stamps launch identity; shared views use wire, no SQL client (19.184394ms)
✔ two wire claims serialize; a lost reply never automatically retries (13.08992ms)
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (7.240817ms)
✔ client preserves UTF-8 when a response divides a multibyte character (11.221426ms)
✔ committed mutation followed by dropped reply reports unknown and is never retried (8.82784ms)
ℹ tests 176
ℹ suites 0
ℹ pass 176
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 6095.062239
@@ -0,0 +1,336 @@
✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (1121.652366ms)
✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (59.477458ms)
✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (146.117245ms)
✔ NO_COLOR: no escape codes, except reverse video on the selected row (204.950193ms)
✔ 1-9 shows the decide flow's own lines and question; only y resolves (48.153ms)
✔ the app shows the same decide text as mosaic decide (19.759474ms)
✔ any key but y or Y at the question answers No; nothing is sent (178.43424ms)
✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (62.923265ms)
✔ under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10) (229.986793ms)
✔ a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12) (29.999435ms)
✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (18.774058ms)
✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (404.340608ms)
✔ quitting at the question answers No and sends nothing, armed or not (39.204203ms)
✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (29.685824ms)
✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (42.891944ms)
✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (278.963934ms)
✔ at every size the decide box shows each line y depends on, or says it is hidden (8145.538976ms)
✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (61.287549ms)
✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (29.828155ms)
✔ help or a detail box cut to fit keeps its last line, Esc included (Dewey) (14.49415ms)
✔ keys: views, moving, trail of a selection, the command line, help and quit (72.306521ms)
✔ one option: the status line says Press 1, not 1-1 (14.620775ms)
✔ no business: the app says so and :business picks one (8.119722ms)
✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (1.805534ms)
✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (163.499123ms)
✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.452085ms)
✔ decide with --note still asks; a No sends nothing (10.275383ms)
✔ the app's business comes from a live bus host only (1.131127ms)
✔ runApp leaves the terminal as it found it on q, end of input, SIGTERM, SIGINT, SIGHUP and an error, the first frame's included (7.020195ms)
✔ draw is pure: same state, same frame (0.769013ms)
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (15.852731ms)
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (12.336739ms)
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (10.039739ms)
✔ decide prints a declining choice as declining (9.108848ms)
✔ an unknown outcome is reported once and never resent (7.699179ms)
✔ a decision closed before the answer arrives exits 2 and points at its trail (7.519771ms)
✔ a prefix that matches two open decisions exits 2 and resolves neither (7.122371ms)
✔ without --business a command uses the live host's business, and a stale host.json is not a host (6.732556ms)
✔ every human command refuses inside an agent run before it touches the bus (6.746574ms)
✔ usage errors exit 4; no business and no host is a usage error (7.562074ms)
✔ agents and tasks print through the broker (6.943048ms)
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.054446ms)
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (31.970092ms)
✔ trackers come from the tracker.* variables of the one project that names a tracker project (55.210151ms)
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (25.950418ms)
✔ two projects that each name a tracker project refuse, since the boot shape holds one (29.214393ms)
✔ a business without tracker.baseUrl gets no trackers entry (26.346839ms)
✔ an unknown business and a broken system config refuse with exit 3 (47.981901ms)
✔ follow prints an unsolicited message after plain talk returned (95.980388ms)
✔ talk --follow continues after reply and prints a later unsolicited message (24.715319ms)
✔ inbox lists all unread human messages under decisions, does not deliver or read them (203.091443ms)
✔ follow refuses agent context and bad arguments (8.652411ms)
✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (8.040659ms)
✔ plain talk leaves the process SIGINT handlers unchanged (8.780067ms)
✔ empty views say so (0.616341ms)
✔ the trail keeps the broker's order and names a decision's task without its rows (0.738756ms)
✔ tasks print the tracker fields the snapshot carries (0.123286ms)
✔ the review loop: the coder pushes and asks, the reviewer fetches and approves, and the task's trail holds each step (283.906056ms)
✔ refusals: no action, non-fast-forward, an unknown commit or request, and a push for another task moves only that task's ref (276.134348ms)
✔ the size cap refuses a push before any byte (132.427401ms)
✔ a fetch over the cap refuses before any byte reaches the reviewer (280.697069ms)
✔ the socket refuses a request with no capability, an unknown one, and a bad request line (138.263086ms)
✔ a rigged clone runs nothing: hooks, fsmonitor, a filter, a gitlink and a symlinked .git/config leave no marker (228.07992ms)
✔ an identity with no role, or of another business, refuses not-a-role and moves no ref (179.584626ms)
✔ a review of a project the business no longer has refuses unknown-project (175.152991ms)
✔ past maxActive connections the socket refuses git-busy, and a hung-up connection frees its slot (120.845109ms)
✔ close() answers host-closing to a connection still sending its request line (120.005731ms)
✔ a bundle that trickles in refuses body-timeout at its deadline and frees its slot (541.181635ms)
✔ a reviewer gone before its bundle was sent is filed fetch-aborted, not as the host's fault (394.674097ms)
✔ a reviewer that hangs up the moment it has the whole bundle was served, not aborted (422.066513ms)
✔ send: a close after every byte left is served; one that fails a queued write is fetch-aborted (40.914244ms)
✔ a bundle with no bytes for bodyIdle refuses body-timeout long before its deadline (434.231135ms)
✔ send: a close once the loop is past its last chunk is served, and a failed write there is fetch-aborted (23.188029ms)
✔ letGo waits for an answer to leave for at most its cap, then destroys the socket (302.328511ms)
✔ a record that fails and can't be undone refuses rollback-failed, which the host logs (310.766495ms)
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (816.234292ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (145.779759ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (77.476775ms)
✔ two bus starts racing for one data root leave one host running; the other refuses that a host already runs (443.610958ms)
✔ a host takes over a host.lock its dead host left, and puts back a live claim that replaced it meanwhile (1.618588ms)
✔ a host.lock that goes between the check and the read is tried again; one that can't be parsed still refuses as unreadable (0.750404ms)
✔ a live host's lock stays, however old the file is (0.491738ms)
✔ a host.lock left by a host killed with SIGKILL doesn't stop the next bus start (651.372967ms)
✔ while a host boots, a reader sees no host: the human commands still ask for --business, stop and status find none (207.314684ms)
✔ a host started before host.lock existed, holding only host.json, still refuses a second start, which leaves no lock (26.750088ms)
✔ a broker fork that throws leaves no host.lock and no host.json (29.520666ms)
✔ a broker send that throws leaves no host.lock and no host.json (25.660704ms)
✔ a broker that misses its boot wait leaves no host.lock and no host.json (27.89985ms)
✔ a notifier fork that throws stops the broker and leaves no host.lock and no host.json (75.983713ms)
✔ a notifier send that throws stops the broker and leaves no host.lock and no host.json (74.439395ms)
✔ 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 (72.107171ms)
✔ 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 (78.124904ms)
✔ a host.json that can't be written stops both children and leaves no host.lock (176.101702ms)
✔ an error event on the broker before it replies fails the start at once, and leaves no host.lock and no live child (28.725944ms)
✔ an error event on the notifier before it replies fails the start at once, and leaves no host.lock and no live child (75.778323ms)
✔ an error event on the broker while the notifier starts fails the start once the notifier replies (171.803051ms)
✔ 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 (176.323753ms)
✔ 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 (169.214461ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1087.406112ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (154.93192ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (76.071655ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (125.563878ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (129.453832ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (81.776118ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (123.790329ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (83.402842ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (138.837902ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (20.956526ms)
✔ bus stop refuses to signal a live pid that is not a bus host (202.571636ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (235.341987ms)
✔ bus start refuses with exit 3 without a notifier config (99.530252ms)
✔ bus start runs until bus stop; status reports it while it runs (648.710281ms)
✔ bus-service.sh renders the unit and installs it into a given directory (55.948587ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (66.913172ms)
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (12.508043ms)
✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (7.833353ms)
✔ each launch rereads limits and model selections; lowered limits keep sessions and log exact hit (21.935809ms)
✖ bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest (122.745684ms)
✖ the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code (112.830992ms)
✖ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (110.494699ms)
✔ a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left (118.483266ms)
✖ a runner that stops at once ends its launch with the runner's reason (114.667119ms)
✔ a Claude Code launch with no session credential refuses before it starts (94.581245ms)
✖ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (111.305512ms)
✖ a runner killed from the host ends its launch as killed, not failed (111.348741ms)
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (92.719059ms)
✖ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (117.008533ms)
node:fs:3413
const stats = binding.lstat(base, true, undefined, true /* throwIfNoEntry */);
^
Error: ENOENT: no such file or directory, lstat '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
at realpathSync (node:fs:3413:29)
at binds (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/sandbox.mjs:103:53)
at spawnSession (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/session.mjs:117:28)
at start (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:257:19)
at async file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:355:16 {
errno: -2,
code: 'ENOENT',
syscall: 'lstat',
path: '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
}
Node.js v26.8.1
✖ 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 (30005.587899ms)
node:fs:3413
const stats = binding.lstat(base, true, undefined, true /* throwIfNoEntry */);
^
Error: ENOENT: no such file or directory, lstat '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
at realpathSync (node:fs:3413:29)
at binds (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/sandbox.mjs:103:53)
at spawnSession (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/session.mjs:117:28)
at start (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:257:19)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:355:16 {
errno: -2,
code: 'ENOENT',
syscall: 'lstat',
path: '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
}
Node.js v26.8.1
✖ 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 (30004.900336ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (89.124771ms)
Interrupted while running:
⚠ packages/cli/tests/launcher.test.mjs (packages/cli/tests/launcher.test.mjs:1:1)
✖ packages/cli/tests/launcher.test.mjs (61320.850548ms)
✖ packages/cli/tests/limits.test.mjs
✖ packages/cli/tests/notifier.test.mjs
✖ packages/cli/tests/other-tree.test.mjs
✖ packages/cli/tests/stack-copy.test.mjs
✖ packages/cli/tests/tester.test.mjs
✖ packages/cli/tests/trackers-boot.test.mjs
✖ packages/cli/tests/transport.test.mjs
✖ packages/cli/tests/verbs.test.mjs
ℹ tests 139
ℹ suites 0
ℹ pass 121
ℹ fail 7
ℹ cancelled 11
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 85300.300681
✖ failing tests:
test at packages/cli/tests/launcher.test.mjs:134:1
✖ bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest (122.745684ms)
Error: ENOENT: no such file or directory, lstat '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
at realpathSync (node:fs:3413:29)
at binds (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/sandbox.mjs:103:53)
at spawnSession (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/session.mjs:117:28)
at start (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:257:19)
at async file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:355:16 {
errno: -2,
code: 'ENOENT',
syscall: 'lstat',
path: '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
}
test at packages/cli/tests/launcher.test.mjs:214:1
✖ the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code (112.830992ms)
Error: ENOENT: no such file or directory, lstat '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
at realpathSync (node:fs:3413:29)
at binds (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/sandbox.mjs:103:53)
at spawnSession (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/session.mjs:117:28)
at start (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:257:19)
at async file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:355:16 {
errno: -2,
code: 'ENOENT',
syscall: 'lstat',
path: '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
}
test at packages/cli/tests/launcher.test.mjs:296:1
✖ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (110.494699ms)
Error: ENOENT: no such file or directory, lstat '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
at realpathSync (node:fs:3413:29)
at binds (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/sandbox.mjs:103:53)
at spawnSession (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/session.mjs:117:28)
at start (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:257:19)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:355:16 {
errno: -2,
code: 'ENOENT',
syscall: 'lstat',
path: '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
}
test at packages/cli/tests/launcher.test.mjs:370:1
✖ a runner that stops at once ends its launch with the runner's reason (114.667119ms)
AssertionError [ERR_ASSERTION]: {"ok":false,"error":"launch-failed"}
false !== true
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/tests/launcher.test.mjs:381:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
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: false,
expected: true,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/launcher.test.mjs:405:1
✖ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (111.305512ms)
Error: ENOENT: no such file or directory, lstat '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
at realpathSync (node:fs:3413:29)
at binds (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/sandbox.mjs:103:53)
at spawnSession (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/session.mjs:117:28)
at start (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:257:19)
at async file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:355:16 {
errno: -2,
code: 'ENOENT',
syscall: 'lstat',
path: '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
}
test at packages/cli/tests/launcher.test.mjs:418:1
✖ a runner killed from the host ends its launch as killed, not failed (111.348741ms)
Error: ENOENT: no such file or directory, lstat '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
at realpathSync (node:fs:3413:29)
at binds (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/sandbox.mjs:103:53)
at spawnSession (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/session.mjs:117:28)
at start (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:257:19)
at async file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:355:16 {
errno: -2,
code: 'ENOENT',
syscall: 'lstat',
path: '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
}
test at packages/cli/tests/launcher.test.mjs:452:1
✖ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (117.008533ms)
Error: ENOENT: no such file or directory, lstat '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
at realpathSync (node:fs:3413:29)
at binds (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/sandbox.mjs:103:53)
at spawnSession (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/seat/src/session.mjs:117:28)
at start (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:257:19)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async file:///mnt/storage/src/mosaic-stack-rocko-49/packages/cli/src/launcher.mjs:355:16 {
errno: -2,
code: 'ENOENT',
syscall: 'lstat',
path: '/mnt/storage/src/mosaic-stack-rocko-49/node_modules'
}
test at packages/cli/tests/launcher.test.mjs:511:3
✖ 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 (30005.587899ms)
'test timed out after 30000ms'
test at packages/cli/tests/launcher.test.mjs:511:3
✖ 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 (30004.900336ms)
'test timed out after 30000ms'
test at packages/cli/tests/launcher.test.mjs:1:1
✖ packages/cli/tests/launcher.test.mjs (61320.850548ms)
'Promise resolution is still pending but the event loop has already resolved'
test at packages/cli/tests/limits.test.mjs:1:1
✖ packages/cli/tests/limits.test.mjs
'Promise resolution is still pending but the event loop has already resolved'
test at packages/cli/tests/notifier.test.mjs:1:1
✖ packages/cli/tests/notifier.test.mjs
'Promise resolution is still pending but the event loop has already resolved'
test at packages/cli/tests/other-tree.test.mjs:1:1
✖ packages/cli/tests/other-tree.test.mjs
'Promise resolution is still pending but the event loop has already resolved'
test at packages/cli/tests/stack-copy.test.mjs:1:1
✖ packages/cli/tests/stack-copy.test.mjs
'Promise resolution is still pending but the event loop has already resolved'
test at packages/cli/tests/tester.test.mjs:1:1
✖ packages/cli/tests/tester.test.mjs
'Promise resolution is still pending but the event loop has already resolved'
test at packages/cli/tests/trackers-boot.test.mjs:1:1
✖ packages/cli/tests/trackers-boot.test.mjs
'Promise resolution is still pending but the event loop has already resolved'
test at packages/cli/tests/transport.test.mjs:1:1
✖ packages/cli/tests/transport.test.mjs
'Promise resolution is still pending but the event loop has already resolved'
test at packages/cli/tests/verbs.test.mjs:1:1
✖ packages/cli/tests/verbs.test.mjs
'Promise resolution is still pending but the event loop has already resolved'
@@ -0,0 +1,105 @@
✔ the boot config is checked before anything starts (17.314653ms)
✔ a business with no tracker entry refuses task verbs (9.867916ms)
✔ credential.expiring and .expired are recorded once per instance (24.030637ms)
✔ a token file that changes on disk records credential.changed (9.455372ms)
✔ autostart polls, reconciles and retries a startup the tracker was down for (32.01509ms)
✔ a refusal a restart must clear is not retried by the poll (7.036527ms)
✔ a poll that fires while two are queued is dropped (8.646611ms)
✔ close waits for a running verb and refuses one that has not started (37.225278ms)
✔ stopped bus resumes an empty task cursor after a long outage, exactly once (29.746394ms)
✔ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (22.759438ms)
✔ commit followed by crash before cache advance neither loses nor duplicates a comment (14.261345ms)
✔ failed event insert rolls back cursor and a retry still emits the comment once (12.632204ms)
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (10.419375ms)
✔ the bundled Vikunja is the pinned upstream image the runbook names (0.656992ms)
✔ every published port is on 127.0.0.1, and no secret is in the file (0.241034ms)
✔ the committed table loads frozen; its verbs are the broker task verbs (1.150872ms)
✔ a write names a verb the table has as its own key, not one it inherits (0.861983ms)
✔ the schema refuses any other shape (1.861007ms)
✔ the Field table in the slice 1 plan names the verbs each field is written by (0.510398ms)
✔ each verb refuses, asks for a decision and writes as the table and the role files say (143.018095ms)
✔ the list holds the PRD's requirement ids, in its order, at its version (1.113812ms)
✔ the committed list loads frozen (0.500614ms)
✔ the schema refuses any other shape (2.155739ms)
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (90.592343ms)
✔ the project list holds recorded project objects in the recorded list envelope (7.293815ms)
✔ the recorded task bodies pass the checks S3 applies to every read (0.412698ms)
✔ the client works against the fake over real HTTP with the platform fetch (85.845387ms)
✔ a correct install starts, and the first reconcile records tasks that already exist (21.689257ms)
✔ verbs refuse while a business is starting and after startup refused it (11.299043ms)
✔ startup refuses a token that can do more than its role needs (29.850515ms)
✔ startup refuses an unsupported version and flags an untested one (21.198916ms)
✔ startup refuses a board that the runbook did not install (24.825318ms)
✔ startup refuses a project the sync bot cannot read (5.746162ms)
✔ startup refuses a configured label the pm bot cannot see (6.837196ms)
✔ startup refuses an expired credential and a missing sync credential (13.084053ms)
✔ an unreachable tracker refuses with tracker-unavailable (5.67549ms)
✔ an edit in the UI is recorded once, with the fields that changed (72.569399ms)
✔ a move between open buckets is seen on the board, though updated does not change (35.79823ms)
✔ a person's comment is counted and a bot's is not (63.964905ms)
✔ the hourly reconcile catches a comment through comment_count (76.335641ms)
✔ a task closed in the UI leaves the open view with its done bucket (110.958185ms)
✔ a task that leaves the board is recorded as deleted, moved or out of reach (63.554461ms)
✔ a poll that read before a verb wrote does not overwrite the verb (33.754481ms)
✔ a tracker fault during a tick is reported and the next tick catches up (34.762895ms)
✔ a malformed answer refuses the tick with tracker-shape (11.816672ms)
✔ no token value reaches the database, the log or a refusal (43.91509ms)
✔ the first look at a task counts only comments inside the window (33.646394ms)
✔ with descendants off the adapter reads no project list and no child, as in v0 (40.168944ms)
✔ a three-level tree is read per project, and a project outside it is not (17.843699ms)
✔ a watch-only child keeps the business ready, and a failed child read refuses as transient (16.743518ms)
✔ a root without the installed buckets refuses with descendants on (6.281475ms)
✔ the project list refuses without read_all, and skips archived projects and saved filters (14.185193ms)
✔ descendants must be true or false (5.62954ms)
✔ a move inside the tree is a moved tombstone plus a snapshot under the new ref (56.395762ms)
✔ a child made after startup joins the tree at the next reconcile (53.335585ms)
✔ a child that leaves the tree has its tasks recorded missing, and the ledger keeps their history (56.5104ms)
✔ a child deleted or hidden between reconciles drops out at the next tick, and the root still refuses (81.451576ms)
✔ a reconcile that fails keeps the previous tree (32.643014ms)
✔ a closed task the cursor missed takes its own project's done bucket (31.653772ms)
✔ closed tasks read at startup take their own project's done bucket (9.184182ms)
✔ a closed task the cursor returns takes its own project's done bucket (33.400693ms)
✔ a task moved out of a child that leaves the tree at the same time is moved, not no-access (29.907575ms)
✔ a task in a bucket outside the five keeps its raw bucket id (28.577174ms)
✔ bucket 0 on a watch-only task never becomes a bucket or a state (76.341918ms)
✔ a child whose kanban view is deleted still refuses the tick, and the reconcile reads it watch-only (54.965019ms)
✔ a child dropped at a tick comes back at the next reconcile once the bot reads it again (33.686472ms)
✔ task.create needs a recorded human request and a requirement id (29.360773ms)
✔ task.create takes only an approved requirement id (18.272543ms)
✔ task.create cites the request a received message carries, not the message's id (13.451082ms)
✔ only labels named in the business file can be written (19.43598ms)
✔ task.schedule sets and clears a due date and relations (23.332302ms)
✔ assign and reassign move the role bots and record task.assigned (46.778211ms)
✔ task.update.assigned is for the assignee and records task.state (44.090524ms)
✔ a wrong expected digest records task.conflict and writes nothing (15.197261ms)
✔ a cross-role verb needs a resolved decision, used once (21.459156ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (21.028012ms)
✔ a task with pushed work closes only citing an approve verdict for its latest push, and a refusal writes nothing (26.448783ms)
✔ a lost answer is settled by a re-read and never retried (22.128748ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (33.937822ms)
✔ a task the sync bot cannot read refuses and records nothing (13.426948ms)
✔ verbs and polls for one business run one at a time (86.31078ms)
✔ a due date with milliseconds is written to the second (63.596374ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (36.948559ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (13.622195ms)
✔ a create whose final read fails succeeds and records task.created (33.518094ms)
✔ an edit between the last write and the final read shows as external on the next poll (38.267517ms)
✔ task.created is recorded when a later label write fails (13.10961ms)
✔ every verb acts in an installed child, on the child's own board (47.289399ms)
✔ task.create refuses a project outside the tree, a watch-only one and one that is not an id (23.243708ms)
✔ with descendants off, task.create takes only the root project (11.790128ms)
✔ a verb on a watch-only task, or a relation to one, refuses with project-not-installed (15.761532ms)
✔ a related task is read before authority: in another project than its ref names, task-moved; missing, task-not-found (21.308136ms)
✔ a task moved to another project of the tree refuses its old ref with task-moved and takes its new one (14.192795ms)
✔ a verb and a create in the child record the final read of the child's board (13.558827ms)
✔ a create in the child whose final read fails records the child's todo bucket (31.934289ms)
✔ a lost answer in the child is settled by a re-read of the child's board (20.832798ms)
✔ every related task is read before authority, not only the first (14.551685ms)
ℹ tests 97
ℹ suites 0
ℹ pass 97
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3405.838395
@@ -0,0 +1,87 @@
[
{
"suite": "pkg-bus",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/bus/tests/agents.test.mjs",
"packages/bus/tests/broker.test.mjs",
"packages/bus/tests/business.test.mjs",
"packages/bus/tests/comment-cursors.test.mjs",
"packages/bus/tests/comment-migration.test.mjs",
"packages/bus/tests/credential-status.test.mjs",
"packages/bus/tests/credentials.test.mjs",
"packages/bus/tests/decision-close.test.mjs",
"packages/bus/tests/decisions.test.mjs",
"packages/bus/tests/end-launch.test.mjs",
"packages/bus/tests/human.test.mjs",
"packages/bus/tests/limits.test.mjs",
"packages/bus/tests/message-citation.test.mjs",
"packages/bus/tests/messages.test.mjs",
"packages/bus/tests/messaging.test.mjs",
"packages/bus/tests/process.test.mjs",
"packages/bus/tests/replies.test.mjs",
"packages/bus/tests/requirements.test.mjs",
"packages/bus/tests/review.test.mjs",
"packages/bus/tests/schema.test.mjs",
"packages/bus/tests/single-use.test.mjs",
"packages/bus/tests/state.test.mjs",
"packages/bus/tests/store.test.mjs",
"packages/bus/tests/task-page.test.mjs",
"packages/bus/tests/task-read.test.mjs",
"packages/bus/tests/tasks.test.mjs",
"packages/bus/tests/tester.test.mjs",
"packages/bus/tests/transport.test.mjs"
]
},
{
"suite": "pkg-tasks",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/tasks/tests/adapter.test.mjs",
"packages/tasks/tests/comment-restart.test.mjs",
"packages/tasks/tests/deploy.test.mjs",
"packages/tasks/tests/fields.test.mjs",
"packages/tasks/tests/requirements.test.mjs",
"packages/tasks/tests/shapes.test.mjs",
"packages/tasks/tests/startup.test.mjs",
"packages/tasks/tests/sync.test.mjs",
"packages/tasks/tests/tree.test.mjs",
"packages/tasks/tests/verbs.test.mjs"
]
},
{
"suite": "pkg-cli",
"exit": 1,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/cli/tests/app.test.mjs",
"packages/cli/tests/cli.test.mjs",
"packages/cli/tests/config.test.mjs",
"packages/cli/tests/follow.test.mjs",
"packages/cli/tests/format.test.mjs",
"packages/cli/tests/git-socket.test.mjs",
"packages/cli/tests/host.test.mjs",
"packages/cli/tests/launch-limits.test.mjs",
"packages/cli/tests/launcher.test.mjs",
"packages/cli/tests/limits.test.mjs",
"packages/cli/tests/notifier.test.mjs",
"packages/cli/tests/other-tree.test.mjs",
"packages/cli/tests/stack-copy.test.mjs",
"packages/cli/tests/tester.test.mjs",
"packages/cli/tests/trackers-boot.test.mjs",
"packages/cli/tests/transport.test.mjs",
"packages/cli/tests/verbs.test.mjs"
]
}
]
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env python3
import json, os, pathlib, signal, subprocess, sys
root=pathlib.Path('/mnt/storage/src/mosaic-stack-rocko-49')
out=pathlib.Path(__file__).resolve().parent/'gate';out.mkdir(exist_ok=True)
steps=[]
for package in ['bus','tasks','cli','webui','harness','seat']:
files=[str(f.relative_to(root)) for f in sorted((root/'packages'/package/'tests').glob('*.test.mjs'))]
steps.append(('pkg-'+package,['node','--test','--test-concurrency=1','--test-reporter=spec',*files]))
steps.append(('script-land-work',['bash','scripts/test-land-work.sh']))
results=[]
for name,command in steps:
with (out/(name+'.log')).open('w') as log:
p=subprocess.Popen(command,cwd=root,env={**os.environ,'DOCKER_HOST':'unix:///nonexistent.sock'},
stdout=log,stderr=subprocess.STDOUT,start_new_session=True)
try: rc=p.wait(timeout=1200)
except subprocess.TimeoutExpired: os.killpg(p.pid,signal.SIGKILL);p.wait();rc='timeout'
results.append(dict(suite=name,exit=rc,command=command))
(out/'results.json').write_text(json.dumps(results,indent=2)+'\n')
print(name,rc,flush=True)
if rc!=0:sys.exit(1)
@@ -0,0 +1,186 @@
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (18.0726ms)
✔ agents: working counts what the holder received and has not read; routed alone is not working (16.776875ms)
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (24.937642ms)
✔ agents: working wins over waiting, and Input needed still shows (9.117675ms)
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (12.753115ms)
✔ agents: state reads only its own business, even for a run name both use (10.434423ms)
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (12.176738ms)
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (11.508931ms)
✔ agents: stranded is oldest first and capped at 50 for a role (57.594964ms)
✔ agents: stranded is per role and per business (19.58298ms)
✔ agents: a message routed to a held role and not yet received is not stranded (7.386457ms)
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (13.82679ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (14.656152ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (15.460275ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (9.493622ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (9.439881ms)
✔ a recommendation that is not exactly one option key refuses invalid-recommendation after the options (14.313793ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (8.894824ms)
✔ task action subjects and linked decision trail are complete and ordered (15.796502ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (6.699842ms)
✔ business isolation includes inherited object names and cross-business message references (10.833233ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (14.850575ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (7.737827ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (11.98167ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (7.834072ms)
✔ both arbiters require human resolution when their cross-role route is themselves (14.377512ms)
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (12.965967ms)
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (1.188116ms)
✔ comment cursors isolate businesses, move only forward and remain append-only (11.96074ms)
✔ only trusted sync writes validated cursors; events and cursor share rollback (7.297847ms)
✔ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (34.747403ms)
✔ migration refuses active writers and drifted legacy schemas, with no partial upgrade (58.865301ms)
✔ named migration registry chains 3b through 3c to a fixture-only future version in order (36.594311ms)
✔ migration refuses mismatched source metadata without changing schema or evidence (28.983697ms)
✔ offline command migrates only the supplied fixture root and reports usage errors (104.329457ms)
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (10.460845ms)
✔ credentials refuses a role session and any argument (10.127004ms)
✔ credentials reads the status on each request, and a broker without one lists none (10.839449ms)
✔ the broker process answers credentials without a token, path or variable in its output, log or store (57.610412ms)
✔ only validated broker references load; returned data and exceptions cannot expose a known token (2.262147ms)
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (1.249245ms)
✔ expiry refuses use and env references never become client data (0.427206ms)
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (0.953329ms)
✔ opaque tokens shorter than 16 characters refuse before use (0.214693ms)
✔ a human resolve sends the raising role one message from the human, and its request is the answer (23.929128ms)
✔ a cross-role decision resolved by its arbiter comes from that role and run, with no request (13.563597ms)
✔ a human withdraw sends one message with the note (12.756137ms)
✔ a human seen before the close leaves the request on the close's answer, not the seen (17.74069ms)
✔ the raising run's withdraw, a supersede, a within-role raise and seen send nothing (17.912782ms)
✔ refused closes send nothing: invalid-choice, decision-closed, not-resolver (16.668141ms)
✔ a raising role nobody holds at the close gets the message on the next claim (15.234374ms)
✔ a human DECISION send keeps its own instruction as the request, not a close's answer (13.830529ms)
✔ roles lists open decisions routed to a role, and never the human inbox (18.105779ms)
✔ recent lists closed decisions of every route, newest close first, with how each closed (17.245748ms)
✔ recent gives the closing event as closed when the decision was seen first (18.513671ms)
✔ recent stops at RECENT rows (57.367473ms)
✔ every row is what trail already gives the same session for that id (23.302507ms)
✔ a role session can't list decisions, so it can't read another role's open decisions (22.351858ms)
✔ decisions is a read: one business, strict arguments, nothing written (22.233524ms)
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (11.680833ms)
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (7.152513ms)
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (8.397838ms)
✔ endLaunch leaves a claim another run took alone (8.773063ms)
✔ refuse records action.refused against the caller with the code only (6.435292ms)
✔ launches off refuses role.launch with launch-revoked until launches on (10.257184ms)
✔ broker process: launch ops authorize role.launch, record refusals and end runs (71.203905ms)
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (1.275133ms)
✔ process reader gets own kernel identity without exposing environment values (0.36235ms)
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.520996ms)
✔ real pid 1 remains inspectable when its environment is protected (0.220532ms)
✔ limits binds actor from human proof and rejects role, reader, tester and forged actor (14.739915ms)
✔ runtime wires verified human settings to the actual shared file writer and actor receipt (29.022343ms)
✔ uncertain organization receipts leave bus startup and reads alone but refuse writes until reconciled (21.969682ms)
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (17.326368ms)
✔ missing and foreign-business citations refuse and roll back message and grant (10.62723ms)
✔ cross-role sends still need a matching resolved decision and consume it once (13.429227ms)
✔ messages pages all human messages newest first without writes; read state is history (212.444232ms)
✔ messages refuses role callers, invalid paging and cursors outside this human feed (15.746617ms)
✔ views messages passes only its paging arguments through (0.215112ms)
✔ messages isolates businesses even with a valid foreign cursor (9.893388ms)
✔ the assistant sends to the user and to the chief of staff only (24.17533ms)
✔ an assistant send to any other role, or to the tester, is refused and logged (19.08546ms)
✔ only the user and the chief of staff may send to the assistant; the tester is refused by default (17.153132ms)
✔ a sender-side list alone refuses a send to a role that has no list (15.733558ms)
✔ a target-side list alone refuses a send from a role that has no list (17.260187ms)
✔ the sender list is checked before the target list (13.642779ms)
✔ roles without the field behave exactly as before (22.778491ms)
✔ a business with no role definitions at all is unlimited (16.397702ms)
✔ a malformed messaging field refuses at boot (7.386471ms)
✔ the assistant's review.request to a reviewer is refused and logged, even with an approved decision (18.115621ms)
✔ a review.verdict to the assistant from a role outside its receiveFrom is refused and logged (19.232548ms)
✔ review requests and verdicts between roles without the field are unchanged (15.344471ms)
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (60.188823ms)
✔ startup token refusal returns safe code without value or partial listening broker (45.226238ms)
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (58.129808ms)
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (91.555498ms)
✔ trusted host registers later launches; socket clients never have a registration verb (56.357222ms)
✔ runtime excludes declared project roots even when host supplies no repoRoots (44.970952ms)
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (56.559453ms)
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (22.08921ms)
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (10.970058ms)
✔ a message with no reply gives an empty list (8.543807ms)
✔ replies come oldest first and stop at REPLIES (48.281659ms)
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (17.776643ms)
✔ strict arguments; a role session gets human-required (19.600949ms)
✔ views passes replies through as one call with the id (0.178546ms)
✔ requirements returns the committed list to a holding role session, and no one else (15.733467ms)
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (8.661343ms)
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (9.270735ms)
✔ a push is recorded as the action.allowed for git.push.working on the task, and only the host records one (18.153079ms)
✔ review.request names the latest pushed commit and sends the reviewer a REVIEW-REQUEST (13.658961ms)
✔ review.request says which run holds the reviewer role, or null, and a null request waits for a claim (15.20457ms)
✔ review.request refuses a commit that isn't the latest push, a non-reviewer and a role without the action (21.463462ms)
✔ reviewFetch answers only the addressed reviewer, for the request it cites (14.14993ms)
✔ review.verdict answers one request once, to the role that asked (16.165243ms)
✔ task.close on a pushed task needs an approve verdict for the latest push (22.189753ms)
✔ a refusal the host decided is filed on the task it names, and only a task ref counts (12.421543ms)
✔ v3b prototype refusals, views and append-only mutations (50.185231ms)
✔ gated approval authorizes once, survives store reopen, and fresh approval works (18.659864ms)
✔ another run cannot consume an approval; a failed check leaves it usable (12.007889ms)
✔ two scheduled callers have exactly one grant and one consumed refusal (8.555251ms)
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (18.745599ms)
✔ class drift gated to cross-role refuses before consumption (12.004218ms)
✔ class drift cross-role to gated refuses before consumption (12.864003ms)
✔ class drift gated to within-role refuses before consumption (11.826685ms)
✔ class drift cross-role to within-role refuses before consumption (13.016785ms)
✔ class drift within-role to gated refuses before consumption (10.665639ms)
✔ class drift within-role to cross-role refuses before consumption (10.700153ms)
✔ message.send consumes approval and prevents a later send or authorize (12.96513ms)
✔ role.revoke consumes approval and prevents a later revoke or authorize (17.747564ms)
✔ a business that never switched launches reads allowed with no time and no event (11.412878ms)
✔ launches revoked and then restored read allowed, with the restore time and event id (8.703518ms)
✔ a task closed on the tracker leaves the open list, and total counts the open ones (9.033301ms)
✔ a tombstone is not open (7.110312ms)
✔ the open list stops at the cap and says so; total still counts every open task (10.125157ms)
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (7.518531ms)
✔ a human session reads the same state, with the same cap (9.592429ms)
✔ reading state writes nothing: every table keeps its row count (8.161166ms)
✔ creates private WAL store and excludes a second writer until explicit close (9.837253ms)
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (9.425037ms)
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (8.94876ms)
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (41.720607ms)
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (6.583885ms)
✔ async transactions refuse before invoking their function (4.979465ms)
✔ the session default fits under the reply cap and pages to the end with next (107.357709ms)
✔ the short form: no description, assignees by role, every listed key (64.790404ms)
✔ each filter, alone and together, and total counts every match (138.579347ms)
✔ task_ref reads one task in full, as the whole board has it (80.646442ms)
✔ no arguments is the read the Console and mosaic make, unchanged (65.713235ms)
✔ an unknown argument or a bad value refuses invalid-request (304.09939ms)
✔ tasks rows carry priority and requirement for every session that reads tasks (24.861154ms)
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (27.823064ms)
✔ an empty business and one with snapshots but no task.created read cleanly (5.980928ms)
✔ recordTask keeps sync reads and a role write apart (15.120671ms)
✔ read_at must be one canonical UTC format, so the projection compares strings safely (5.83325ms)
✔ a bad entry refuses the whole record (6.421079ms)
✔ taskView reads the projection for one business (7.42201ms)
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (14.07288ms)
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (281.792883ms)
✔ without an adapter the server refuses every task verb (15.442981ms)
✔ the runtime refuses an invalid adapter and closes a valid one (15.132483ms)
✔ the process loads the S3 adapter from plain-data trackers (110.2661ms)
✔ the tester binds only when the business sets bus.tester to exactly true (48.995999ms)
✔ a business may not name a role tester, so the tester address is never a role (12.908115ms)
✔ every verb outside the tester list refuses tester-refused and is recorded against the tester (30.857581ms)
✔ a tester message is from tester, starts [mos test], and writes no human.input or authority (12.951493ms)
✔ a role answers a tester only in reply to a tester message, and the answer never reaches the human (15.772385ms)
✔ the tester reads the human views a reader reads (17.064712ms)
✔ tester proof binds its own CLI, start time and nonce, refuses a managed run and allows an agent (1.082345ms)
✔ neither CLI's nonce proves the other: each proof is bound to its own CLI and nonce variable (0.327914ms)
✔ testerNonce reads only the tester's variable and gives null on any read error (0.187745ms)
✔ an envelope carries exactly one of cap, human and tester; no tester authenticator refuses (14.905855ms)
✔ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (591.41324ms)
✔ socket capability stamps launch identity; shared views use wire, no SQL client (19.098642ms)
✔ two wire claims serialize; a lost reply never automatically retries (14.182425ms)
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (8.354323ms)
✔ client preserves UTF-8 when a response divides a multibyte character (11.250544ms)
✔ committed mutation followed by dropped reply reports unknown and is never retried (8.891815ms)
ℹ tests 178
ℹ suites 0
ℹ pass 178
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 6225.595082
@@ -0,0 +1,205 @@
✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (1131.456583ms)
✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (58.120119ms)
✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (139.421181ms)
✔ NO_COLOR: no escape codes, except reverse video on the selected row (207.952015ms)
✔ 1-9 shows the decide flow's own lines and question; only y resolves (48.636173ms)
✔ the app shows the same decide text as mosaic decide (21.134775ms)
✔ any key but y or Y at the question answers No; nothing is sent (174.770503ms)
✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (57.007814ms)
✔ under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10) (222.375161ms)
✔ a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12) (30.408824ms)
✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (19.44225ms)
✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (390.416391ms)
✔ quitting at the question answers No and sends nothing, armed or not (29.532601ms)
✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (28.662636ms)
✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (44.476171ms)
✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (290.460586ms)
✔ at every size the decide box shows each line y depends on, or says it is hidden (8077.378001ms)
✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (63.021149ms)
✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (30.974953ms)
✔ help or a detail box cut to fit keeps its last line, Esc included (Dewey) (14.54113ms)
✔ keys: views, moving, trail of a selection, the command line, help and quit (75.983753ms)
✔ one option: the status line says Press 1, not 1-1 (16.057421ms)
✔ no business: the app says so and :business picks one (8.431433ms)
✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (1.768245ms)
✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (169.495219ms)
✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.400948ms)
✔ decide with --note still asks; a No sends nothing (9.521648ms)
✔ the app's business comes from a live bus host only (0.988477ms)
✔ 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.857319ms)
✔ draw is pure: same state, same frame (0.734297ms)
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (14.596726ms)
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (11.216971ms)
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (9.252602ms)
✔ decide prints a declining choice as declining (8.237693ms)
✔ an unknown outcome is reported once and never resent (7.375108ms)
✔ a decision closed before the answer arrives exits 2 and points at its trail (7.515498ms)
✔ a prefix that matches two open decisions exits 2 and resolves neither (7.047251ms)
✔ without --business a command uses the live host's business, and a stale host.json is not a host (6.74696ms)
✔ every human command refuses inside an agent run before it touches the bus (6.88021ms)
✔ usage errors exit 4; no business and no host is a usage error (7.374595ms)
✔ agents and tasks print through the broker (6.811365ms)
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.066409ms)
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (29.411399ms)
✔ trackers come from the tracker.* variables of the one project that names a tracker project (58.013196ms)
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (27.217546ms)
✔ two projects that each name a tracker project refuse, since the boot shape holds one (26.366462ms)
✔ a business without tracker.baseUrl gets no trackers entry (26.701526ms)
✔ an unknown business and a broken system config refuse with exit 3 (47.368364ms)
✔ follow prints an unsolicited message after plain talk returned (24.383509ms)
✔ talk --follow continues after reply and prints a later unsolicited message (21.908989ms)
✔ inbox lists all unread human messages under decisions, does not deliver or read them (190.772267ms)
✔ follow refuses agent context and bad arguments (8.810271ms)
✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (8.748044ms)
✔ plain talk leaves the process SIGINT handlers unchanged (9.081367ms)
✔ empty views say so (0.552504ms)
✔ the trail keeps the broker's order and names a decision's task without its rows (0.697926ms)
✔ tasks print the tracker fields the snapshot carries (0.137188ms)
✔ the review loop: the coder pushes and asks, the reviewer fetches and approves, and the task's trail holds each step (295.124997ms)
✔ refusals: no action, non-fast-forward, an unknown commit or request, and a push for another task moves only that task's ref (280.549371ms)
✔ the size cap refuses a push before any byte (134.175945ms)
✔ a fetch over the cap refuses before any byte reaches the reviewer (283.232588ms)
✔ the socket refuses a request with no capability, an unknown one, and a bad request line (131.574218ms)
✔ a rigged clone runs nothing: hooks, fsmonitor, a filter, a gitlink and a symlinked .git/config leave no marker (226.79686ms)
✔ an identity with no role, or of another business, refuses not-a-role and moves no ref (174.108327ms)
✔ a review of a project the business no longer has refuses unknown-project (168.123063ms)
✔ past maxActive connections the socket refuses git-busy, and a hung-up connection frees its slot (117.21618ms)
✔ close() answers host-closing to a connection still sending its request line (113.268063ms)
✔ a bundle that trickles in refuses body-timeout at its deadline and frees its slot (534.329974ms)
✔ a reviewer gone before its bundle was sent is filed fetch-aborted, not as the host's fault (380.867788ms)
✔ a reviewer that hangs up the moment it has the whole bundle was served, not aborted (538.400794ms)
✔ send: a close after every byte left is served; one that fails a queued write is fetch-aborted (41.008301ms)
✔ a bundle with no bytes for bodyIdle refuses body-timeout long before its deadline (431.598333ms)
✔ send: a close once the loop is past its last chunk is served, and a failed write there is fetch-aborted (44.760492ms)
✔ letGo waits for an answer to leave for at most its cap, then destroys the socket (302.256337ms)
✔ a record that fails and can't be undone refuses rollback-failed, which the host logs (317.012764ms)
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (816.51652ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (150.523651ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (90.264186ms)
✔ two bus starts racing for one data root leave one host running; the other refuses that a host already runs (445.292146ms)
✔ a host takes over a host.lock its dead host left, and puts back a live claim that replaced it meanwhile (1.668611ms)
✔ a host.lock that goes between the check and the read is tried again; one that can't be parsed still refuses as unreadable (0.737147ms)
✔ a live host's lock stays, however old the file is (0.49289ms)
✔ a host.lock left by a host killed with SIGKILL doesn't stop the next bus start (651.148049ms)
✔ while a host boots, a reader sees no host: the human commands still ask for --business, stop and status find none (192.632425ms)
✔ a host started before host.lock existed, holding only host.json, still refuses a second start, which leaves no lock (25.880752ms)
✔ a broker fork that throws leaves no host.lock and no host.json (28.441835ms)
✔ a broker send that throws leaves no host.lock and no host.json (29.170023ms)
✔ a broker that misses its boot wait leaves no host.lock and no host.json (27.830115ms)
✔ a notifier fork that throws stops the broker and leaves no host.lock and no host.json (77.48177ms)
✔ a notifier send that throws stops the broker and leaves no host.lock and no host.json (74.025809ms)
✔ 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 (77.278937ms)
✔ 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 (77.733845ms)
✔ a host.json that can't be written stops both children and leaves no host.lock (171.864239ms)
✔ an error event on the broker before it replies fails the start at once, and leaves no host.lock and no live child (28.011242ms)
✔ an error event on the notifier before it replies fails the start at once, and leaves no host.lock and no live child (77.61208ms)
✔ an error event on the broker while the notifier starts fails the start once the notifier replies (178.318612ms)
✔ 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 (167.882863ms)
✔ 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 (166.734956ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1081.193583ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (159.920744ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (81.003533ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (124.179343ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (126.11229ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (72.711683ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (122.416861ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (79.571814ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (155.317416ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (26.18894ms)
✔ bus stop refuses to signal a live pid that is not a bus host (203.212399ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (271.241869ms)
✔ bus start refuses with exit 3 without a notifier config (110.921359ms)
✔ bus start runs until bus stop; status reports it while it runs (736.896734ms)
✔ bus-service.sh renders the unit and installs it into a given directory (72.455188ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (93.719175ms)
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (17.213694ms)
✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (12.75944ms)
✔ each launch rereads limits and model selections; lowered limits keep sessions and log exact hit (21.214148ms)
✔ bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest (1205.2367ms)
✔ the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code (402.001985ms)
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (3506.847457ms)
✔ a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left (113.940976ms)
✔ a runner that stops at once ends its launch with the runner's reason (186.098661ms)
✔ a Claude Code launch with no session credential refuses before it starts (90.636589ms)
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (164.860555ms)
✔ a runner killed from the host ends its launch as killed, not failed (231.953812ms)
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (95.232227ms)
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (937.691851ms)
✔ 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 (603.636217ms)
✔ 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 (3549.372427ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (84.128303ms)
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (92.842817ms)
✔ a launch client that never closes its side doesn't hold the host's close (88.921593ms)
✔ a runner that ignores SIGTERM is killed when the host closes (1202.789156ms)
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (4.383835ms)
✔ migration needs no bus/config, prints whole converted document, is idempotent and never writes (8.358277ms)
✔ limits view/set use the human bus transport and pass only the explicit patch (0.952992ms)
✔ post-119 example and Jason fixture migrate with checked inherited models; wrong or absent config refuses (157.024585ms)
✔ zoned uses the IANA zone across DST (11.921454ms)
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (14.279928ms)
✔ two blocking decisions get two DMs with different nonces (9.204216ms)
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.158994ms)
✔ a failed DM is journaled, backs off, and is retried until it lands (9.416039ms)
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (10.273711ms)
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (11.720936ms)
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (8.354491ms)
✔ 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 (65.108365ms)
✔ a restart after the second refusal does not send before that refusal's 30 min are up (31.384463ms)
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (8.984149ms)
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (6.720052ms)
✔ an inbox read failure is logged and the next poll retries (0.498648ms)
✔ no Discord id reaches the journal or the log (7.253134ms)
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (1.684233ms)
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (1.83131ms)
✔ the journal: a whole file that is one torn line truncates to empty (1.14407ms)
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.40844ms)
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.514678ms)
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.723801ms)
✔ the journal: a symlinked directory refuses and says it is a link (0.273471ms)
✔ 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.44175ms)
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.325261ms)
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.427081ms)
✔ digest content stays within Discord's 2000 characters (0.224717ms)
✔ runLoop never overlaps ticks and stops after the one in flight (110.317845ms)
✔ a human command refused with human-required prints the other-tree line and exits 3 (1.835984ms)
✔ a --tester command refused with tester-required prints the other-tree line and exits 3 (0.6998ms)
✔ other exit-3 refusals print no other-tree line (1.031995ms)
✔ the line names no path (0.10438ms)
✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (340.000184ms)
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (373.309452ms)
✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (371.748173ms)
✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (246.58929ms)
✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (3029.181736ms)
✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (432.718868ms)
✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (147.220265ms)
✔ install never shows a template name on systemd 261; template drop-ins still refuse (153.970984ms)
✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (139.3928ms)
✔ inherited mosaic-.service.d override refuses without changing the unit (156.126031ms)
✔ inherited service.d override refuses without changing the unit (156.78321ms)
✔ inherited drop-in unreadable directory or file refuses before writing (137.808649ms)
✔ talk --tester sends a [mos test] message as tester from inside an agent and prints the role's reply (21.921316ms)
✔ talk --tester refuses text without the [mos test] prefix before any call (7.625146ms)
✔ the read verbs take --tester inside an agent, and the trail shows the tester as the sender (11.492424ms)
✔ decide, stop and launches take no --tester (8.111199ms)
✔ the tester transport runs tester-cli.mjs, and the tester's refusals exit 3 (0.287687ms)
✔ with no stand-in transport, --tester runs tester-cli.mjs on the data root's socket (0.43304ms)
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 (240.256594ms)
✔ the transport writes {business, verb, args} to the child and reads its JSON (25.944424ms)
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2124.15454ms)
✔ busExit and refuseInsideAgent (0.506287ms)
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (41.063771ms)
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1029.884472ms)
✔ a late reply the Console has listed still reaches the next talk (2031.870118ms)
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (10.542966ms)
✔ launches off and on go to the broker and change the business's launch state (8.67139ms)
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (8.677366ms)
ℹ tests 195
ℹ suites 0
ℹ pass 195
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 49052.247764
@@ -0,0 +1,92 @@
✔ sessionModel: agent vars win, then the system's execution settings (7.880525ms)
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (3.468916ms)
✔ a chief-of-staff bundle lists launch among the bus tools and the manifest's typed tools (1.678507ms)
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (1.913034ms)
✔ the prompt's built-in tools line is the harness's own names, as the manifest lists them (5.835988ms)
✔ the prompt names the project clones in the workspace, and none when there are none (3.854181ms)
✔ the prompt explains the review loop to a role with push_working, and reviews to one with fetch_review (5.169287ms)
✔ a bundle is written once: an existing file refuses (1.09281ms)
✔ a path with a single quote can't go into the hook command (1.415052ms)
✔ allow exits 0, a deny exits 2 with the reason on stderr (89.137653ms)
✔ a missing or wrong policy, or a bad event, exits 2 (71.470185ms)
✔ the bundle's wrapped command: a missing gate or node still blocks (1080.72187ms)
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (620.145966ms)
✔ claude: the hook alone blocks a path outside the workspace (369.625247ms)
✔ claude: a second turn resumes the first turn's session (643.645968ms)
✔ claude adapter: --restricted is always passed (4.98042ms)
✔ claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable (15.556464ms)
✔ claude adapter: a directory pin exits 2 before it runs; a pin with a space runs (5.418784ms)
✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (644.20235ms)
✔ claude: a missing hook or MCP file refuses before claude starts (7.346147ms)
✔ pi: policy tools and typed tools pass, anything else is blocked (2.388288ms)
✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.548783ms)
✔ file tool paths must resolve inside the workspace (0.870998ms)
✔ pi's own path normalisation can't be used to step out (0.690427ms)
✔ a symlink inside the workspace that points out is outside (0.601458ms)
✔ a dangling symlink is refused at any depth, in both harnesses (2.041946ms)
✔ read is checked under every spelling pi's read would open, in both harnesses (10.132327ms)
✔ other spellings cover directories, dangling links and pi's cwd (0.9835ms)
✔ a relative path climbs from the workspace's real path, in both harnesses (3.122048ms)
✔ claude path fields per tool (0.501216ms)
✔ glob patterns stay inside the workspace (0.460037ms)
✔ a path that can't be checked is blocked (0.28747ms)
✔ fetch_review's have lists the working refs before origin's, and no more than HAVE_MAX (159.175003ms)
✔ fetch_review's have orders the working refs newest first within each clone, one clone after another (113.964301ms)
✔ a runner git past its time: SIGTERM reaches its process group, and SIGKILL follows after the grace period (1809.910991ms)
✔ once a signalled runner git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (303.422681ms)
✔ initialize, ping and tools/list (32.251342ms)
✔ tools/call goes through the tool socket; a refusal is an isError result (26.605103ms)
✔ replies are compact JSON; over the cap a tool error names the size and the cap (33.043512ms)
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (25.144155ms)
✔ a missing argument is a usage error (23.318106ms)
✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (305.900298ms)
✔ pi: a reply over the cap is a tool error naming the size and the cap; list_tasks asks for open tasks (301.661418ms)
✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (304.792022ms)
✔ pi: a read is refused when pi would open another spelling outside (301.543742ms)
✔ pi: a relative path climbs from the real path of a workspace behind a symlink (289.257408ms)
✔ pi: a relative path climbs from the real path of a dataRoot behind a symlink (292.942855ms)
✔ pi: a missing extension refuses before any model call (6.203793ms)
✔ pi: an extension without its configuration fails pi's start (235.729728ms)
✔ founderCheck: founder variables, then a needed service without a usable token (0.8964ms)
✔ turnRequest names the sender, class, reply and decision (0.159771ms)
✔ turnRequest says a tester's message is a test and never Jason's instruction (0.251106ms)
✔ turnRequest shows the message's own sent time, a day old as well as new (0.131427ms)
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.198894ms)
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (149.472315ms)
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (166.743317ms)
✔ a task closed on the tracker is missing from the next turn's open list (165.906823ms)
✔ a message read a day after it was sent shows the time it was sent (88.418801ms)
✔ a decision the human resolves becomes a turn citing the decision and the answer (137.894872ms)
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (61.514628ms)
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (46.071514ms)
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (246.153293ms)
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1185.705348ms)
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (91.07011ms)
✔ founder credentials stop before the claim (20) (84.458581ms)
✔ a refused claim exits 21; an ended run's capability exits 22 (92.161734ms)
✔ the launch ending under a running session exits 22 (98.2236ms)
✔ a broker that stays unreachable exits 23 after brokerRetries polls (196.114385ms)
✔ a broker that is down at the claim exits 23, not 21 (42.265215ms)
✔ no capability, or a malformed one, on stdin exits 2 (144.108979ms)
✔ a missing or malformed policy exits 2 before the claim (80.431429ms)
✔ the PM gets its task verbs and the reads, and no launch (5.320443ms)
✔ the chief of staff gets launch and send_message, and no task verbs (1.454924ms)
✔ the assistant gets send_message and the reads, no launch (1.669687ms)
✔ list_requirements reads the requirement ids, for a task.create holder with a tracker (2.228087ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (0.978613ms)
✔ a coder with a tracker gets its own task tools and no task_reassign (0.979534ms)
✔ launch only when the business's launch block names the instance as launcher (1.673752ms)
✔ an action outside the instance's authority has no tool (1.346717ms)
✔ the review loop: push_working and request_review for the coder, fetch_review and review_verdict for the reviewer, none for the PM (2.910057ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (6.475424ms)
✔ list_tasks takes the broker's filters and pages; get_task takes a task_ref (1.855425ms)
✔ callTool sends list_tasks done false unless the session names done; other tools as given (1.860916ms)
✔ replyText: compact JSON up to the cap, an error naming the size and the cap over it (0.640863ms)
ℹ tests 84
ℹ suites 0
ℹ pass 84
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 19086.783641
@@ -0,0 +1,145 @@
✔ a first launch clones the root at its branch, with the root's path as origin (30.814015ms)
✔ the clone is not at its place until git clone has finished (20.026989ms)
✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (57.446875ms)
✔ a clone ahead of the root keeps its commits and counts as current (36.080347ms)
▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves
✔ a modified file (41.975897ms)
✔ an untracked file (42.394501ms)
✔ a staged file (42.863196ms)
✔ another branch (40.571095ms)
✔ a detached HEAD (40.797834ms)
✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (209.696205ms)
✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (41.72769ms)
▶ a clone kept before the branch check, or for a failed status, gets no fetch
✔ not-a-clone (22.444109ms)
✔ hooks-present (23.64668ms)
✔ submodule (29.332051ms)
✔ config-changed (26.913897ms)
✔ root-unreadable (26.098494ms)
✔ root-detached (31.641575ms)
✔ status-failed, on the root's branch and clean (34.243798ms)
✔ status-failed, dirty (33.628961ms)
✔ a clone kept before the branch check, or for a failed status, gets no fetch (229.088255ms)
✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (37.081784ms)
✔ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (44.383561ms)
✔ a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge (72.001105ms)
▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it
✔ user.name (26.018712ms)
✔ user.email (26.363696ms)
✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (52.847706ms)
✔ a commit git can't read after the fast-forward is null (46.657246ms)
✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (19.274281ms)
▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it
✔ core.fsmonitor (34.271422ms)
✔ core.hooksPath (33.966516ms)
✔ a hook file (31.109331ms)
✔ include.path (33.413953ms)
✔ includeIf (34.016606ms)
✔ a filter driver (33.843312ms)
✔ origin moved (34.347227ms)
✔ a second origin url (34.851059ms)
✔ the origin url twice, both the root (36.514182ms)
✔ an ext:: origin (34.637871ms)
✔ a branch tracking another remote (34.473758ms)
✔ a branch merging a tag (33.453804ms)
✔ a fetch line onto local branches (34.404405ms)
✔ no origin (33.872563ms)
✔ a config file over the size cap (30.774817ms)
✔ config a symlink to /dev/zero (31.311459ms)
✔ the clone a symlink to the bait (30.976568ms)
✔ the clone a file (31.104943ms)
✔ the clone a plain directory (30.730901ms)
✔ .git a gitdir: file (30.121057ms)
✔ .git a symlink (30.911802ms)
✔ .git/logs a symlink (30.677716ms)
✔ .git/refs/heads a symlink (31.637689ms)
✔ .git/objects a symlink (30.776609ms)
✔ a reflog hard-linked to a file outside (30.760581ms)
✔ HEAD a fifo (34.410164ms)
✔ a commondir file (39.903169ms)
✔ an alternates file (43.702223ms)
✔ a gitlink staged, no .gitmodules (44.530393ms)
✔ a gitlink and .gitmodules staged (45.160145ms)
✔ a gitlink to a repository with its own gitlink (52.127784ms)
✔ a gitlink and .gitmodules committed (48.387573ms)
✔ a gitlink and submodule.active in config (45.334415ms)
✔ a .git/modules with no gitlink (34.247653ms)
✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1209.152306ms)
▶ a root that can't be cloned refuses with the project id and leaves nothing behind
✔ missing (10.596408ms)
✔ not a repository (10.508374ms)
✔ a plain directory inside another repository (15.108963ms)
✔ unreadable (10.712174ms)
✔ on no branch (12.037759ms)
✔ missing an object (15.600063ms)
✔ a root that can't be cloned refuses with the project id and leaves nothing behind (75.332628ms)
✔ a clone whose root is on no branch, or gone, is kept and named (29.845742ms)
✔ no credential or forge remote of the root reaches a clone (49.754809ms)
✔ every project in the business file gets its own clone at its own branch (35.77472ms)
✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (514.118049ms)
✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1018.030284ms)
✔ a timeout signals git's process group, so a process git started dies with it (514.494641ms)
✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (516.195318ms)
✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (1.645592ms)
✔ sandboxProblem names a missing bwrap and a bwrap that fails (8.657319ms)
✔ every bind says why; the bundle and session file stay read-only over the run directory (1.203097ms)
✔ the host's git socket is bound only when given, read-only and before the writable binds (0.859728ms)
✔ the git identity is the instance's, at <instance>@<business>.mosaic.invalid, and sandboxArgv refuses without it (1.356127ms)
✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (1766.25384ms)
✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2648.999451ms)
✔ a confined session that runs tester-cli.mjs is refused as tester, and the same shim outside the sandbox binds (211.424116ms)
✔ resolveSeat: by name under --repo resolves the repo layout (1.301243ms)
✔ resolveSeat: by path resolves the fleet layout (0.357989ms)
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.741351ms)
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.672294ms)
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.885207ms)
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.311949ms)
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.210543ms)
✔ CLI launch: registers, execs the fake launch script, and passes args through (29.083784ms)
✔ CLI launch: --harness lands in the record (30.471548ms)
✔ CLI launch: the launch script's own exit code passes through (33.439309ms)
✔ CLI launch: relaunching a seat rewrites the one registration record (56.065651ms)
✔ CLI launch: omitting --task records an empty string, not null (29.044123ms)
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (80.55892ms)
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (137.113804ms)
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.377661ms)
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.554236ms)
✔ 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.699041ms)
✔ updateTask: records setBy, preserves startedAt and every unrelated field, upgrades an old record in place only when the task is set, and replaces a previous attribution (1.114176ms)
✔ 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 (252.78003ms)
✔ REQ-LIMIT-1: family substring matching is no longer exported (0.744983ms)
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.685444ms)
✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.248315ms)
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.50821ms)
✔ session file and launch log: 0600, the session file written once (1.005989ms)
✔ endReason maps the runner's exit codes; a signal is killed (0.116729ms)
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.231537ms)
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (0.936078ms)
✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (188.845858ms)
✔ workRef and validBranch take only what a ref name can hold (1.123378ms)
✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (98.768506ms)
✔ a rewritten branch refuses non-fast-forward and the work ref stays (99.80719ms)
✔ a branch with no history in common with main refuses unrelated-history (47.412052ms)
✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (43.79553ms)
✔ main is fetched from the root again before each push, so a clone of a newer root pushes (120.704892ms)
✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (73.346571ms)
✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (70.509882ms)
✔ only a v2 bundle with the named branch is read (56.092274ms)
✔ a branch named main, or named for another task, moves only this task's work ref (96.493234ms)
✔ a record that throws moves the work ref back, or removes a new one (134.717964ms)
✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (360.616955ms)
✔ a root that can't be read refuses root-unreadable (37.628529ms)
✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2136.308964ms)
✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1027.540399ms)
✔ a working repository path that isn't a directory refuses (24.502593ms)
✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (98.677203ms)
✔ review of a project with no working repository refuses unknown-commit (12.760326ms)
✔ the runner's git tools hold the host's limits and branch rule (1.123418ms)
ℹ tests 132
ℹ suites 0
ℹ pass 132
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 15142.521329
@@ -0,0 +1,105 @@
✔ the boot config is checked before anything starts (13.629955ms)
✔ a business with no tracker entry refuses task verbs (9.901902ms)
✔ credential.expiring and .expired are recorded once per instance (21.944276ms)
✔ a token file that changes on disk records credential.changed (10.29513ms)
✔ autostart polls, reconciles and retries a startup the tracker was down for (33.510163ms)
✔ a refusal a restart must clear is not retried by the poll (7.335997ms)
✔ a poll that fires while two are queued is dropped (10.688979ms)
✔ close waits for a running verb and refuses one that has not started (39.674804ms)
✔ stopped bus resumes an empty task cursor after a long outage, exactly once (31.961928ms)
✔ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (26.099796ms)
✔ commit followed by crash before cache advance neither loses nor duplicates a comment (16.036128ms)
✔ failed event insert rolls back cursor and a retry still emits the comment once (12.907531ms)
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (10.896774ms)
✔ the bundled Vikunja is the pinned upstream image the runbook names (0.740586ms)
✔ every published port is on 127.0.0.1, and no secret is in the file (0.286942ms)
✔ the committed table loads frozen; its verbs are the broker task verbs (1.162806ms)
✔ a write names a verb the table has as its own key, not one it inherits (0.941715ms)
✔ the schema refuses any other shape (1.808027ms)
✔ the Field table in the slice 1 plan names the verbs each field is written by (0.493906ms)
✔ each verb refuses, asks for a decision and writes as the table and the role files say (133.933499ms)
✔ the list holds the PRD's requirement ids, in its order, at its version (1.158317ms)
✔ the committed list loads frozen (0.50695ms)
✔ the schema refuses any other shape (2.053206ms)
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (80.116418ms)
✔ the project list holds recorded project objects in the recorded list envelope (7.174548ms)
✔ the recorded task bodies pass the checks S3 applies to every read (0.404514ms)
✔ the client works against the fake over real HTTP with the platform fetch (89.341426ms)
✔ a correct install starts, and the first reconcile records tasks that already exist (21.630253ms)
✔ verbs refuse while a business is starting and after startup refused it (12.02468ms)
✔ startup refuses a token that can do more than its role needs (29.174946ms)
✔ startup refuses an unsupported version and flags an untested one (19.924014ms)
✔ startup refuses a board that the runbook did not install (24.68159ms)
✔ startup refuses a project the sync bot cannot read (5.755174ms)
✔ startup refuses a configured label the pm bot cannot see (6.751915ms)
✔ startup refuses an expired credential and a missing sync credential (12.796302ms)
✔ an unreachable tracker refuses with tracker-unavailable (5.255529ms)
✔ an edit in the UI is recorded once, with the fields that changed (71.470331ms)
✔ a move between open buckets is seen on the board, though updated does not change (36.439166ms)
✔ a person's comment is counted and a bot's is not (62.780444ms)
✔ the hourly reconcile catches a comment through comment_count (75.350571ms)
✔ a task closed in the UI leaves the open view with its done bucket (109.900649ms)
✔ a task that leaves the board is recorded as deleted, moved or out of reach (61.894273ms)
✔ a poll that read before a verb wrote does not overwrite the verb (33.486326ms)
✔ a tracker fault during a tick is reported and the next tick catches up (33.368087ms)
✔ a malformed answer refuses the tick with tracker-shape (10.737421ms)
✔ no token value reaches the database, the log or a refusal (43.331139ms)
✔ the first look at a task counts only comments inside the window (32.345604ms)
✔ with descendants off the adapter reads no project list and no child, as in v0 (40.043243ms)
✔ a three-level tree is read per project, and a project outside it is not (17.748236ms)
✔ a watch-only child keeps the business ready, and a failed child read refuses as transient (16.877198ms)
✔ a root without the installed buckets refuses with descendants on (5.901551ms)
✔ the project list refuses without read_all, and skips archived projects and saved filters (13.977911ms)
✔ descendants must be true or false (6.169436ms)
✔ a move inside the tree is a moved tombstone plus a snapshot under the new ref (57.040089ms)
✔ a child made after startup joins the tree at the next reconcile (52.048415ms)
✔ a child that leaves the tree has its tasks recorded missing, and the ledger keeps their history (56.856341ms)
✔ a child deleted or hidden between reconciles drops out at the next tick, and the root still refuses (80.24672ms)
✔ a reconcile that fails keeps the previous tree (32.267805ms)
✔ a closed task the cursor missed takes its own project's done bucket (31.637608ms)
✔ closed tasks read at startup take their own project's done bucket (8.61474ms)
✔ a closed task the cursor returns takes its own project's done bucket (33.676788ms)
✔ a task moved out of a child that leaves the tree at the same time is moved, not no-access (30.473087ms)
✔ a task in a bucket outside the five keeps its raw bucket id (29.468364ms)
✔ bucket 0 on a watch-only task never becomes a bucket or a state (77.383366ms)
✔ a child whose kanban view is deleted still refuses the tick, and the reconcile reads it watch-only (54.885724ms)
✔ a child dropped at a tick comes back at the next reconcile once the bot reads it again (33.432815ms)
✔ task.create needs a recorded human request and a requirement id (29.557851ms)
✔ task.create takes only an approved requirement id (16.441379ms)
✔ task.create cites the request a received message carries, not the message's id (13.539503ms)
✔ only labels named in the business file can be written (18.519991ms)
✔ task.schedule sets and clears a due date and relations (23.353458ms)
✔ assign and reassign move the role bots and record task.assigned (47.867571ms)
✔ task.update.assigned is for the assignee and records task.state (43.780171ms)
✔ a wrong expected digest records task.conflict and writes nothing (17.038807ms)
✔ a cross-role verb needs a resolved decision, used once (21.667749ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (20.898162ms)
✔ a task with pushed work closes only citing an approve verdict for its latest push, and a refusal writes nothing (26.951691ms)
✔ a lost answer is settled by a re-read and never retried (22.807109ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (34.157908ms)
✔ a task the sync bot cannot read refuses and records nothing (14.047826ms)
✔ verbs and polls for one business run one at a time (87.191096ms)
✔ a due date with milliseconds is written to the second (61.114605ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (35.516461ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (12.603896ms)
✔ a create whose final read fails succeeds and records task.created (31.174387ms)
✔ an edit between the last write and the final read shows as external on the next poll (35.845088ms)
✔ task.created is recorded when a later label write fails (12.684216ms)
✔ every verb acts in an installed child, on the child's own board (39.88197ms)
✔ task.create refuses a project outside the tree, a watch-only one and one that is not an id (21.965358ms)
✔ with descendants off, task.create takes only the root project (11.244863ms)
✔ a verb on a watch-only task, or a relation to one, refuses with project-not-installed (17.934748ms)
✔ a related task is read before authority: in another project than its ref names, task-moved; missing, task-not-found (21.386007ms)
✔ a task moved to another project of the tree refuses its old ref with task-moved and takes its new one (15.343634ms)
✔ a verb and a create in the child record the final read of the child's board (14.569025ms)
✔ a create in the child whose final read fails records the child's todo bucket (34.37208ms)
✔ a lost answer in the child is settled by a re-read of the child's board (18.932468ms)
✔ every related task is read before authority, not only the first (15.042806ms)
ℹ tests 97
ℹ suites 0
ℹ pass 97
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3368.95934
@@ -0,0 +1,122 @@
✔ 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 (2953.91428ms)
✔ 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 (4924.742697ms)
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (1060.205228ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (1120.382781ms)
✔ the README says a refused field table names the file or the key at fault, never a path (0.708114ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (2730.802951ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (54.882783ms)
✔ decisions route serves the roles and recent lists from the reader view (27.06034ms)
✔ credentials route serves the broker credential status from the reader view (7.548149ms)
✔ replies route serves the replies to one message from the reader view (25.525087ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (47.442231ms)
✔ bus refusals map to statuses and never carry a path or a stack (24.281782ms)
✔ humanCall speaks the human transport protocol without blocking the server (1636.995326ms)
✔ busReader takes --business, else the live bus host, else refuses (142.24143ms)
✔ humanCall kills a transport that runs past its timeout (527.539618ms)
✔ serve refuses a --business value that is not a business id (208.733971ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (916.163971ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1263.189899ms)
✔ conversation view: a newer session with no readable history keeps the marker (832.027183ms)
✔ conversation view: seats without history say so and offer no reply (541.086384ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (560.853897ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (51647.683824ms)
✔ messages HTTP read pages and preserves metadata; bad arguments refuse before a read; other reads unchanged (68.278142ms)
✔ 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 (732.563013ms)
✔ Messages nav count polls on Tasks and Board and clears on a Board refusal; messages 403 refuses local pages (1588.441809ms)
✔ copy writes the Console paths at the full id, read-only, and leaves an existing copy alone (557.279562ms)
✔ copy refuses a commit off refactor, an unknown one, a worktree and a bad copies directory (514.919712ms)
✔ copy writes nothing when an archived path is missing at the commit (138.291706ms)
✔ a copy that loses the rename race leaves no read-only temp directory behind (337.152895ms)
✔ a copy whose rename fails with no target refuses, writes no copy and leaves no temp directory (323.440868ms)
✔ a copy that meets a target holding files says exists: and moves nothing into it (322.35431ms)
✔ a copy that meets an empty target directory replaces it by rename, not by moving into it (417.375268ms)
✔ serve runs from a copy: /healthz 200, the config check, roles and RELEASE from the copy, the queue from --checkout (630.985292ms)
✔ serve refuses a --checkout that is not an absolute directory path (214.468798ms)
✔ consoleReads runs the queue read under queueRoot and reads RELEASE under root (50.225812ms)
✔ preview options are all three or none, and each is checked (5.635288ms)
✔ the CLI refuses a partial or invalid preview flag set at start (483.911487ms)
✔ the CLI with all three flags serves the preview mode (105.773157ms)
✔ a request for the public name needs the header equal to the user id, else 401 and nothing read (13.823371ms)
✔ the header name matches without regard to case, and the value compare is exact (2.962758ms)
✔ the public name with the header reads the bus views, and the Host and Origin are checked first (21.85639ms)
✔ in preview mode every write answers 403 read-only, from the public name and loopback, and reaches nothing (8.329298ms)
✔ loopback requests are unchanged in preview mode, and the bus views work with the board down (3.453609ms)
✔ a proxy that rewrites Host to the loopback port gets 403 on its first request, whatever the user (12.540119ms)
✔ a repeated Host is 400 before any other check, in preview mode and without it (5.512835ms)
✔ without the preview flags the public name is refused and writes still reach the board (3.481001ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.102131ms)
✔ credential state from the date alone, as the bus states it (0.126564ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (306.908737ms)
✔ queue: a refused read fails closed with the store's text, redacted (159.239178ms)
✔ queue: a store that fails to load, or a child that dies, gives a fixed message with no path or stack (695.982126ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (9.244748ms)
✔ business: missing or invalid file refuses with the module's text, redacted (5.413253ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (5.960073ms)
✔ settings: release, board, loopback address, notifier binding name only (91.165708ms)
✔ task fields: the committed table with no reads configured, GET only; a bad table refuses with no path (16.156014ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (648.575634ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (20621.127884ms)
✔ 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 (2141.52702ms)
✔ a sent receipt lists its replies as the page refreshes, oldest first and capped, and marks none delivered or read (10911.803578ms)
✔ startServer takes send only as a function and only with the preview mode (5.044922ms)
✔ the CLI refuses --send without the preview flags, and without a configured bus (335.163729ms)
✔ the CLI with --send and a configured bus sends through the human transport and logs one line (107.058744ms)
✔ without --send the route is read-only like every other write, and config says no send (5.771527ms)
✔ with --send every other write is still 403 read-only, and nothing reaches the board or bus (4.756679ms)
✔ config says send only on the public name with --send on (1.517372ms)
✔ a send without the signed-in user is 401 and is neither sent nor logged, loopback included (3.149924ms)
✔ each request check refuses before anything is sent, and logs the refusal without the body (10.497804ms)
✔ a send passes exactly to, body and in_reply_to, answers the id, and logs it (2.20328ms)
✔ a crafted role name cannot forge a log line (1.67614ms)
✔ a bus refusal is not sent, with its status; an outcome the bus could not confirm is unknown and never retried (5.368638ms)
✔ through the human transport the request is exactly the business, message.send and the keys (70.893922ms)
✔ against a real broker: the message is written once as a human send, and its refusals map (19.300515ms)
✔ loopback host and board origin fail closed (5.245331ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (61.973408ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (39.238197ms)
✔ unreachable board reports URL; CLI rejects unsupported options (310.217111ms)
✔ render prints the unit without the preview flags (8.463284ms)
✔ render carries every serve flag, the preview set included (5.910101ms)
✔ a partial preview set is refused (13.116683ms)
✔ a value the unit cannot carry is refused, for every flag (218.041498ms)
✔ the rendered unit names none of the human proof agent markers and sets no agent variable (15.810568ms)
✔ install into --dir with --no-reload writes the file whole, then reports unchanged (43.934141ms)
✔ install without --no-reload runs daemon-reload and nothing that starts the unit (19.036315ms)
✔ uninstall refuses while the unit is running, with or without --no-reload, and removes it once it is not (97.307852ms)
✔ status prints the unit state and GET /healthz on the installed port (149.434817ms)
✔ usage: an unknown verb or flag, and start or enable, exit 4 (51.603277ms)
✔ without --send, render is the template with its placeholders filled, byte for byte (24.176124ms)
✔ render --send with the preview set ends ExecStart with --send and changes nothing else (38.350676ms)
✔ --send without the whole preview set is refused and writes nothing (97.836146ms)
✔ install --send into --dir with --no-reload writes the sending unit, and status says whether it sends (184.247219ms)
✔ uninstall and status take no --send (27.216946ms)
✔ install refuses while the unit is running, in any state but inactive or failed, and changes nothing (200.586642ms)
✔ when the state read fails, install and uninstall refuse with one line and change nothing (51.069488ms)
✔ install needs --copy naming a copy that exists, and refuses outside the canonical checkout without --allow-worktree (46.388876ms)
✔ install refuses unless the bus unit runs from the same copy, naming both paths, and changes nothing (169.201585ms)
✔ render --copy runs the copy and reads the queue in the checkout; without --copy the unit is unchanged (13.521184ms)
✔ the uid header takes only an Authentik uid; the username header still takes a username (235.730187ms)
✔ a preview value cannot start with - or . (24.380381ms)
✔ status names the copy and whether refactor has moved past it, and says when the unit runs from no copy (338.846372ms)
✔ Settings form shows every scope and held role; gated save, exact payload, retained draft and no automatic retry (954.232149ms)
2026-10-15T16:16:06.468Z settings user="jason" changed
✔ REQ-WEB-1 signed-in same-origin Settings write reaches exactly one human operation (13.834684ms)
✔ REQ-WEB-1 Settings writes require enabled send, named Host, identity, exact Origin and same-origin Fetch Metadata (5.564984ms)
✔ REQ-WEB-1 Settings validates JSON, size, revision and strict writable keys before broker call (4.575844ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.696392ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.314905ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.692209ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5349.69571ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2201.556788ms)
✔ Queue, Business and Settings in their five states, with no move button (2947.791789ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1024.50571ms)
✔ the server log names no config path when the system config refuses (212.038764ms)
ℹ tests 112
ℹ suites 0
ℹ pass 112
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 131123.758343
@@ -0,0 +1,161 @@
[
{
"suite": "pkg-bus",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/bus/tests/agents.test.mjs",
"packages/bus/tests/broker.test.mjs",
"packages/bus/tests/business.test.mjs",
"packages/bus/tests/comment-cursors.test.mjs",
"packages/bus/tests/comment-migration.test.mjs",
"packages/bus/tests/credential-status.test.mjs",
"packages/bus/tests/credentials.test.mjs",
"packages/bus/tests/decision-close.test.mjs",
"packages/bus/tests/decisions.test.mjs",
"packages/bus/tests/end-launch.test.mjs",
"packages/bus/tests/human.test.mjs",
"packages/bus/tests/limits.test.mjs",
"packages/bus/tests/message-citation.test.mjs",
"packages/bus/tests/messages.test.mjs",
"packages/bus/tests/messaging.test.mjs",
"packages/bus/tests/process.test.mjs",
"packages/bus/tests/replies.test.mjs",
"packages/bus/tests/requirements.test.mjs",
"packages/bus/tests/review.test.mjs",
"packages/bus/tests/schema.test.mjs",
"packages/bus/tests/single-use.test.mjs",
"packages/bus/tests/state.test.mjs",
"packages/bus/tests/store.test.mjs",
"packages/bus/tests/task-page.test.mjs",
"packages/bus/tests/task-read.test.mjs",
"packages/bus/tests/tasks.test.mjs",
"packages/bus/tests/tester.test.mjs",
"packages/bus/tests/transport.test.mjs"
]
},
{
"suite": "pkg-tasks",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/tasks/tests/adapter.test.mjs",
"packages/tasks/tests/comment-restart.test.mjs",
"packages/tasks/tests/deploy.test.mjs",
"packages/tasks/tests/fields.test.mjs",
"packages/tasks/tests/requirements.test.mjs",
"packages/tasks/tests/shapes.test.mjs",
"packages/tasks/tests/startup.test.mjs",
"packages/tasks/tests/sync.test.mjs",
"packages/tasks/tests/tree.test.mjs",
"packages/tasks/tests/verbs.test.mjs"
]
},
{
"suite": "pkg-cli",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/cli/tests/app.test.mjs",
"packages/cli/tests/cli.test.mjs",
"packages/cli/tests/config.test.mjs",
"packages/cli/tests/follow.test.mjs",
"packages/cli/tests/format.test.mjs",
"packages/cli/tests/git-socket.test.mjs",
"packages/cli/tests/host.test.mjs",
"packages/cli/tests/launch-limits.test.mjs",
"packages/cli/tests/launcher.test.mjs",
"packages/cli/tests/limits.test.mjs",
"packages/cli/tests/notifier.test.mjs",
"packages/cli/tests/other-tree.test.mjs",
"packages/cli/tests/stack-copy.test.mjs",
"packages/cli/tests/tester.test.mjs",
"packages/cli/tests/trackers-boot.test.mjs",
"packages/cli/tests/transport.test.mjs",
"packages/cli/tests/verbs.test.mjs"
]
},
{
"suite": "pkg-webui",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/webui/tests/board-held.test.mjs",
"packages/webui/tests/browser-edge.test.mjs",
"packages/webui/tests/browser.test.mjs",
"packages/webui/tests/bus-browser.test.mjs",
"packages/webui/tests/bus.test.mjs",
"packages/webui/tests/conversation.test.mjs",
"packages/webui/tests/discord.test.mjs",
"packages/webui/tests/history-return-flow.test.mjs",
"packages/webui/tests/messages.test.mjs",
"packages/webui/tests/pinned-copy.test.mjs",
"packages/webui/tests/preview.test.mjs",
"packages/webui/tests/reads.test.mjs",
"packages/webui/tests/relaunch.test.mjs",
"packages/webui/tests/return-flow.test.mjs",
"packages/webui/tests/send-browser.test.mjs",
"packages/webui/tests/send.test.mjs",
"packages/webui/tests/serve.test.mjs",
"packages/webui/tests/service.test.mjs",
"packages/webui/tests/settings-form.test.mjs",
"packages/webui/tests/settings-write.test.mjs",
"packages/webui/tests/shell.test.mjs",
"packages/webui/tests/views.test.mjs"
]
},
{
"suite": "pkg-harness",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/harness/tests/bundle.test.mjs",
"packages/harness/tests/claude-gate.test.mjs",
"packages/harness/tests/claude-session.test.mjs",
"packages/harness/tests/gate.test.mjs",
"packages/harness/tests/git-tools.test.mjs",
"packages/harness/tests/mcp-server.test.mjs",
"packages/harness/tests/pi-session.test.mjs",
"packages/harness/tests/runner.test.mjs",
"packages/harness/tests/tools.test.mjs"
]
},
{
"suite": "pkg-seat",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/seat/tests/checkout.test.mjs",
"packages/seat/tests/sandbox.test.mjs",
"packages/seat/tests/seat.test.mjs",
"packages/seat/tests/session.test.mjs",
"packages/seat/tests/working.test.mjs"
]
},
{
"suite": "script-land-work",
"exit": 0,
"command": [
"bash",
"scripts/test-land-work.sh"
]
}
]
@@ -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,16 @@
✔ the boot config is checked before anything starts (12.798887ms)
✔ a business with no tracker entry refuses task verbs (10.016191ms)
✔ credential.expiring and .expired are recorded once per instance (27.542602ms)
✔ a token file that changes on disk records credential.changed (13.042913ms)
✔ autostart polls, reconciles and retries a startup the tracker was down for (33.845629ms)
✔ a refusal a restart must clear is not retried by the poll (6.91246ms)
✔ a poll that fires while two are queued is dropped (8.694878ms)
✔ close waits for a running verb and refuses one that has not started (37.517795ms)
ℹ tests 8
ℹ suites 0
ℹ pass 8
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 217.170355
@@ -0,0 +1,13 @@
✔ stopped bus resumes an empty task cursor after a long outage, exactly once (27.868582ms)
✔ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (22.01672ms)
✔ commit followed by crash before cache advance neither loses nor duplicates a comment (14.596605ms)
✔ failed event insert rolls back cursor and a retry still emits the comment once (12.359056ms)
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (10.642195ms)
ℹ tests 5
ℹ suites 0
ℹ pass 5
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 155.68026
@@ -0,0 +1,20 @@
✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (329.961857ms)
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (368.201634ms)
✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (362.665471ms)
✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (243.666219ms)
✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (2902.923188ms)
✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (419.86055ms)
✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (146.350641ms)
✔ install never shows a template name on systemd 261; template drop-ins still refuse (150.939192ms)
✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (131.777818ms)
✔ inherited mosaic-.service.d override refuses without changing the unit (155.930079ms)
✔ inherited service.d override refuses without changing the unit (154.266269ms)
✔ inherited drop-in unreadable directory or file refuses before writing (134.586347ms)
ℹ tests 12
ℹ suites 0
ℹ pass 12
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5557.378466
@@ -0,0 +1,10 @@
✔ comment cursors isolate businesses, move only forward and remain append-only (12.403291ms)
✔ only trusted sync writes validated cursors; events and cursor share rollback (7.392897ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 71.458997
@@ -0,0 +1,11 @@
✔ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (38.746013ms)
✔ migration refuses active writers and drifted legacy schemas, with no partial upgrade (57.998919ms)
✔ named migration registry chains 3b through 3c to a fixture-only future version in order (36.131812ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 185.10879
@@ -0,0 +1,9 @@
✔ v3b prototype refusals, views and append-only mutations (51.482534ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 101.449473
@@ -0,0 +1,19 @@
✔ an edit in the UI is recorded once, with the fields that changed (72.343428ms)
✔ a move between open buckets is seen on the board, though updated does not change (35.26329ms)
✔ a person's comment is counted and a bot's is not (62.726388ms)
✔ the hourly reconcile catches a comment through comment_count (75.197639ms)
✔ a task closed in the UI leaves the open view with its done bucket (113.519552ms)
✔ a task that leaves the board is recorded as deleted, moved or out of reach (62.450006ms)
✔ a poll that read before a verb wrote does not overwrite the verb (34.073533ms)
✔ a tracker fault during a tick is reported and the next tick catches up (34.711051ms)
✔ a malformed answer refuses the tick with tracker-shape (11.792419ms)
✔ no token value reaches the database, the log or a refusal (43.103079ms)
✔ the first look at a task counts only comments inside the window (32.279025ms)
ℹ tests 11
ℹ suites 0
ℹ pass 11
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 644.534818
@@ -0,0 +1,118 @@
packages/bus/src/broker.mjs
packages/bus/src/business.mjs
packages/bus/src/client.mjs
packages/bus/src/credentials.mjs
packages/bus/src/human-cli.mjs
packages/bus/src/human.mjs
packages/bus/src/index.mjs
packages/bus/src/migrate-schema.mjs
packages/bus/src/process.mjs
packages/bus/src/runtime.mjs
packages/bus/src/server.mjs
packages/bus/src/store.mjs
packages/bus/src/tester-cli.mjs
packages/bus/src/tester.mjs
packages/bus/tests/agents.test.mjs
packages/bus/tests/broker.test.mjs
packages/bus/tests/business.test.mjs
packages/bus/tests/comment-cursors.test.mjs
packages/bus/tests/comment-migration.test.mjs
packages/bus/tests/credential-status.test.mjs
packages/bus/tests/credentials.test.mjs
packages/bus/tests/decision-close.test.mjs
packages/bus/tests/decisions.test.mjs
packages/bus/tests/end-launch.test.mjs
packages/bus/tests/human.test.mjs
packages/bus/tests/limits.test.mjs
packages/bus/tests/message-citation.test.mjs
packages/bus/tests/messages.test.mjs
packages/bus/tests/messaging.test.mjs
packages/bus/tests/process.test.mjs
packages/bus/tests/replies.test.mjs
packages/bus/tests/requirements.test.mjs
packages/bus/tests/review.test.mjs
packages/bus/tests/single-use.test.mjs
packages/bus/tests/state.test.mjs
packages/bus/tests/store.test.mjs
packages/bus/tests/task-page.test.mjs
packages/bus/tests/task-read.test.mjs
packages/bus/tests/tasks.test.mjs
packages/bus/tests/tester.test.mjs
packages/bus/tests/transport.test.mjs
packages/cli/src/app.mjs
packages/cli/src/cli.mjs
packages/cli/src/config.mjs
packages/cli/src/index.mjs
packages/cli/src/launcher.mjs
packages/cli/src/limits.mjs
packages/cli/src/notifier-process.mjs
packages/cli/tests/app.test.mjs
packages/cli/tests/cli.test.mjs
packages/cli/tests/config.test.mjs
packages/cli/tests/follow.test.mjs
packages/cli/tests/git-socket.test.mjs
packages/cli/tests/helpers.mjs
packages/cli/tests/host.test.mjs
packages/cli/tests/launch-limits.test.mjs
packages/cli/tests/launcher.test.mjs
packages/cli/tests/limits.test.mjs
packages/cli/tests/notifier.test.mjs
packages/cli/tests/other-tree.test.mjs
packages/cli/tests/stack-copy.test.mjs
packages/cli/tests/tester.test.mjs
packages/cli/tests/trackers-boot.test.mjs
packages/cli/tests/transport.test.mjs
packages/cli/tests/verbs.test.mjs
packages/harness/src/runner.mjs
packages/harness/tests/claude-session.test.mjs
packages/harness/tests/pi-session.test.mjs
packages/harness/tests/runner.test.mjs
packages/harness/tests/tools.test.mjs
packages/seat/tests/sandbox.test.mjs
packages/seat/tests/session.test.mjs
packages/tasks/src/adapter.mjs
packages/tasks/src/digest.mjs
packages/tasks/src/fake.mjs
packages/tasks/src/fields.mjs
packages/tasks/src/index.mjs
packages/tasks/src/startup.mjs
packages/tasks/src/sync.mjs
packages/tasks/src/verbs.mjs
packages/tasks/src/vikunja.mjs
packages/tasks/tests/adapter.test.mjs
packages/tasks/tests/comment-restart.test.mjs
packages/tasks/tests/fields.test.mjs
packages/tasks/tests/shapes.test.mjs
packages/tasks/tests/startup.test.mjs
packages/tasks/tests/sync.test.mjs
packages/tasks/tests/tree.test.mjs
packages/tasks/tests/verbs.test.mjs
packages/tasks/tests/world.mjs
packages/webui/src/cli.mjs
packages/webui/src/reads.mjs
packages/webui/src/serve.mjs
packages/webui/tests/board-held.test.mjs
packages/webui/tests/browser-edge.test.mjs
packages/webui/tests/browser.test.mjs
packages/webui/tests/bus-browser.test.mjs
packages/webui/tests/bus.test.mjs
packages/webui/tests/conversation.test.mjs
packages/webui/tests/discord.test.mjs
packages/webui/tests/fixture.mjs
packages/webui/tests/history-fixture.mjs
packages/webui/tests/history-return-flow.test.mjs
packages/webui/tests/messages.test.mjs
packages/webui/tests/pinned-copy.test.mjs
packages/webui/tests/preview.test.mjs
packages/webui/tests/reads.test.mjs
packages/webui/tests/relaunch.test.mjs
packages/webui/tests/return-flow.test.mjs
packages/webui/tests/send-browser.test.mjs
packages/webui/tests/send.test.mjs
packages/webui/tests/serve.test.mjs
packages/webui/tests/settings-form.test.mjs
packages/webui/tests/settings-write.test.mjs
packages/webui/tests/shell.test.mjs
packages/webui/tests/views.test.mjs
scripts/land-work/fixture.mjs
scripts/land-work/read.mjs
@@ -0,0 +1,5 @@
16:export const SCHEMA_SHA256 = '179ffe356d4ff19a49b5ebad39b6c6bfd7771deb8e1b7c55b5e746f039d69e65';
27:if (hex(ddl) !== SCHEMA_SHA256) throw Error('schema-source-mismatch');
107: this.run('INSERT INTO meta(key,value) VALUES (?,?)', 'schema_version', '3b');
113: this.get("SELECT value FROM meta WHERE key='schema_version'")?.value !== '3b' ||
179ffe356d4ff19a49b5ebad39b6c6bfd7771deb8e1b7c55b5e746f039d69e65 -
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""One deliberate fault per check. Restore bytes, isolate process groups, never count timeouts as kills."""
import hashlib,json,os,pathlib,signal,subprocess,sys
root=pathlib.Path('/mnt/storage/src/mosaic-stack-rocko-49')
out=pathlib.Path(__file__).resolve().parent/'mutants';out.mkdir(exist_ok=True)
restart='packages/tasks/tests/comment-restart.test.mjs'
cursors='packages/bus/tests/comment-cursors.test.mjs'
migration='packages/bus/tests/comment-migration.test.mjs'
cases=[]
def add(id,path,old,new,test): cases.append(dict(id=id,path=path,old=old,new=new,test=test))
adapter='packages/tasks/src/adapter.mjs';sync='packages/tasks/src/sync.mjs';broker='packages/bus/src/broker.mjs';store='packages/bus/src/store.mjs'
add('01-restore-durable-ids',adapter,'new Map(comments.cursors.map((c) => [c.task_id, c.last_comment]))','new Map()',restart)
add('02-persist-observed-ids',broker,'for (const x of commentCursors)', 'for (const x of [])',restart)
add('03-startup-comment-read',sync,'if (before === undefined || count !== before)','if (before !== undefined && count !== before)',restart)
add('04-exclude-bots',sync,'fresh && !ctx.bots.has(c.author.id)','fresh',restart)
add('05-strict-new-id',sync,'c.id > last','c.id >= last',restart)
add('06-cache-after-commit',sync,' if (out.snapshots.length || out.events.length || out.commentCursors.length)',
' for (const c of out.commentCursors) ctx.lastComment.set(c.task_id, c.last_comment);\n if (out.snapshots.length || out.events.length || out.commentCursors.length)',restart)
add('07-event-cursor-atomicity',sync,'ctx.broker.recordTask({ business: ctx.business, ...out });',
'ctx.broker.recordTask({ business: ctx.business, ...out, commentCursors: [] });\n if (out.commentCursors.length) ctx.broker.recordTask({ business: ctx.business, commentCursors: out.commentCursors });',restart)
add('08-no-empty-poll-row',sync,'if (top > (last ?? 0))','if (true)',restart)
add('09-initial-clock-margin',sync,'ctx.commentEpoch - WINDOW_MS','ctx.commentEpoch',restart)
add('10-durable-initial-epoch',adapter,'commentEpoch: Date.parse(comments.epoch)','commentEpoch: clock()',restart)
add('11-validate-cursor-input',broker,"if (![x.task_id, x.last_comment].every((n) => Number.isSafeInteger(n) && n > 0))",'if (false)',cursors)
add('12-sync-only-cursor-write',broker,"if (cap !== null && commentCursors.length) fail('agent-required');",'/* sync-only check omitted */',cursors)
add('13-business-isolation',broker,"ORDER BY task_id`, business,","ORDER BY task_id`, 'demo',",cursors)
add('14-explicit-offline-only',store,'if (!fresh && migrateSchema) this.#migrateSchema();','if (!fresh) this.#migrateSchema();',migration)
add('15-pinned-source-metadata',store,'this.get("SELECT value FROM meta WHERE key=\'schema_digest\'")?.value !== step.fromDigest ||','false ||',migration)
add('16-chain-all-steps',store,'while (version !== SCHEMA_VERSION)','if (version !== SCHEMA_VERSION)',migration)
add('17-preserve-source-copy',store,'this.#db.exec(step.sql);',"this.#db.exec(step.sql);\n this.#db.exec(\"DROP TRIGGER events_no_delete; DELETE FROM events; CREATE TRIGGER events_no_delete BEFORE DELETE ON events BEGIN SELECT RAISE(ABORT, 'events is append-only'); END\");",migration)
add('18-land-work-version','scripts/land-work/read.mjs',"meta('schema_version') !== SCHEMA_VERSION","meta('schema_version') !== '3b'",'scripts/test-land-work.sh')
add('21-exclusive-writer-lock',store,"fd = openSync(this.#lock, 'wx', 0o600);","fd = openSync(this.#lock, 'w', 0o600);",migration)
add('22-migration-command','packages/bus/src/migrate-schema.mjs','migrateSchema: true','migrateSchema: false',migration)
results=[]
for c in cases:
file=root/c['path'];before=file.read_bytes();source=before.decode();n=source.count(c['old'])
if n!=1: raise RuntimeError(f"{c['id']}: expected one mutation site, found {n}")
file.write_text(source.replace(c['old'],c['new']))
try:
command=['bash',c['test']] if c['test'].endswith('.sh') else ['node','--test',c['test']]
with (out/(c['id']+'.log')).open('w') as log:
p=subprocess.Popen(command,cwd=root,env={**os.environ,'DOCKER_HOST':'unix:///nonexistent.sock'},
stdout=log,stderr=subprocess.STDOUT,start_new_session=True)
try: rc=p.wait(timeout=180)
except subprocess.TimeoutExpired: os.killpg(p.pid,signal.SIGKILL);p.wait();rc='timeout'
content=(out/(c['id']+'.log')).read_text()
invalid=any(x in content for x in ['SyntaxError:', 'ERR_MODULE_NOT_FOUND', 'schema-source-mismatch', 'migration-target-mismatch'])
verdict='INVALID' if invalid else 'TIMEOUT' if rc=='timeout' else 'SURVIVED' if rc==0 else 'KILLED'
result={k:c[k] for k in ['id','path','test']};result.update(result=verdict,exit=rc)
results.append(result);(out/'results.json').write_text(json.dumps(results,indent=2)+'\n')
print(c['id'],verdict,rc,flush=True)
finally:file.write_bytes(before)
if verdict!='KILLED':sys.exit(1)
sys.exit(0)
@@ -0,0 +1,40 @@
✔ stopped bus resumes an empty task cursor after a long outage, exactly once (28.005928ms)
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (18.960515ms)
✖ commit followed by crash before cache advance neither loses nor duplicates a comment (14.603286ms)
✔ failed event insert rolls back cursor and a retry still emits the comment once (11.999457ms)
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (11.083573ms)
ℹ tests 5
ℹ suites 0
ℹ pass 3
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 152.910563
✖ failing tests:
test at packages/tasks/tests/comment-restart.test.mjs:31:1
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (18.960515ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
2 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:52: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: 2,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/tasks/tests/comment-restart.test.mjs:61:1
✖ commit followed by crash before cache advance neither loses nor duplicates a comment (14.603286ms)
Error: adapter-failed
at Object.restart (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/world.mjs:157:56)
at async TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:69:3)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
@@ -0,0 +1,49 @@
✔ stopped bus resumes an empty task cursor after a long outage, exactly once (28.517177ms)
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (10.900855ms)
✖ commit followed by crash before cache advance neither loses nor duplicates a comment (17.175169ms)
✔ failed event insert rolls back cursor and a retry still emits the comment once (11.629073ms)
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (10.342446ms)
ℹ tests 5
ℹ suites 0
ℹ pass 3
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 143.625558
✖ failing tests:
test at packages/tasks/tests/comment-restart.test.mjs:31:1
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (10.900855ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:37: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: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/tasks/tests/comment-restart.test.mjs:61:1
✖ commit followed by crash before cache advance neither loses nor duplicates a comment (17.175169ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
2 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:70: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: 2,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,49 @@
✖ stopped bus resumes an empty task cursor after a long outage, exactly once (25.941362ms)
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (17.089876ms)
✔ commit followed by crash before cache advance neither loses nor duplicates a comment (15.120332ms)
✔ failed event insert rolls back cursor and a retry still emits the comment once (12.028781ms)
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (11.259771ms)
ℹ tests 5
ℹ suites 0
ℹ pass 3
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 148.303354
✖ failing tests:
test at packages/tasks/tests/comment-restart.test.mjs:17:1
✖ stopped bus resumes an empty task cursor after a long outage, exactly once (25.941362ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:24:10)
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: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/tasks/tests/comment-restart.test.mjs:31:1
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (17.089876ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
1 !== 2
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:51: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: 1,
expected: 2,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,32 @@
✔ stopped bus resumes an empty task cursor after a long outage, exactly once (29.183359ms)
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (18.710754ms)
✔ commit followed by crash before cache advance neither loses nor duplicates a comment (14.073767ms)
✔ failed event insert rolls back cursor and a retry still emits the comment once (11.617748ms)
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (10.721692ms)
ℹ tests 5
ℹ suites 0
ℹ pass 4
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 152.517053
✖ failing tests:
test at packages/tasks/tests/comment-restart.test.mjs:31:1
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (18.710754ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
2 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:52: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: 2,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,49 @@
✔ stopped bus resumes an empty task cursor after a long outage, exactly once (28.316491ms)
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (19.895057ms)
✖ commit followed by crash before cache advance neither loses nor duplicates a comment (15.111054ms)
✔ failed event insert rolls back cursor and a retry still emits the comment once (11.651132ms)
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (11.241306ms)
ℹ tests 5
ℹ suites 0
ℹ pass 3
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 156.324758
✖ failing tests:
test at packages/tasks/tests/comment-restart.test.mjs:31:1
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (19.895057ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
3 !== 2
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:51: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: 3,
expected: 2,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/tasks/tests/comment-restart.test.mjs:61:1
✖ commit followed by crash before cache advance neither loses nor duplicates a comment (15.111054ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
2 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:70: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: 2,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,32 @@
✔ stopped bus resumes an empty task cursor after a long outage, exactly once (28.545386ms)
✔ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (22.402873ms)
✔ commit followed by crash before cache advance neither loses nor duplicates a comment (14.808901ms)
✖ failed event insert rolls back cursor and a retry still emits the comment once (13.910082ms)
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (10.751415ms)
ℹ tests 5
ℹ suites 0
ℹ pass 4
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 163.092499
✖ failing tests:
test at packages/tasks/tests/comment-restart.test.mjs:75:1
✖ failed event insert rolls back cursor and a retry still emits the comment once (13.910082ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:82: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: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,32 @@
✔ stopped bus resumes an empty task cursor after a long outage, exactly once (29.254906ms)
✔ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (23.04829ms)
✖ commit followed by crash before cache advance neither loses nor duplicates a comment (15.866562ms)
✔ failed event insert rolls back cursor and a retry still emits the comment once (12.728213ms)
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (10.650272ms)
ℹ tests 5
ℹ suites 0
ℹ pass 4
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 160.600869
✖ failing tests:
test at packages/tasks/tests/comment-restart.test.mjs:61:1
✖ commit followed by crash before cache advance neither loses nor duplicates a comment (15.866562ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
2 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:70: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: 2,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,75 @@
✖ stopped bus resumes an empty task cursor after a long outage, exactly once (19.782582ms)
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (8.479875ms)
✖ commit followed by crash before cache advance neither loses nor duplicates a comment (8.142129ms)
✖ failed event insert rolls back cursor and a retry still emits the comment once (9.392503ms)
✖ initial cursor accepts a comment within the stated one-minute clock skew margin (8.950959ms)
ℹ tests 5
ℹ suites 0
ℹ pass 0
ℹ fail 5
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 128.140802
✖ failing tests:
test at packages/tasks/tests/comment-restart.test.mjs:17:1
✖ stopped bus resumes an empty task cursor after a long outage, exactly once (19.782582ms)
Error: invalid-request
at fail (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:76:9)
at file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:613:90
at Store.transaction (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/store.mjs:213:22)
at Broker.recordTask (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:610:24)
at record (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/src/sync.mjs:169:16)
at reconcile (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/src/sync.mjs:264:3) {
code: 'invalid-request'
}
test at packages/tasks/tests/comment-restart.test.mjs:31:1
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (8.479875ms)
Error: invalid-request
at fail (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:76:9)
at file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:613:90
at Store.transaction (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/store.mjs:213:22)
at Broker.recordTask (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:610:24)
at record (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/src/sync.mjs:169:16)
at reconcile (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/src/sync.mjs:264:3) {
code: 'invalid-request'
}
test at packages/tasks/tests/comment-restart.test.mjs:61:1
✖ commit followed by crash before cache advance neither loses nor duplicates a comment (8.142129ms)
Error: invalid-request
at fail (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:76:9)
at file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:613:90
at Store.transaction (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/store.mjs:213:22)
at Broker.recordTask (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:610:24)
at record (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/src/sync.mjs:169:16)
at reconcile (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/src/sync.mjs:264:3) {
code: 'invalid-request'
}
test at packages/tasks/tests/comment-restart.test.mjs:75:1
✖ failed event insert rolls back cursor and a retry still emits the comment once (9.392503ms)
Error: invalid-request
at fail (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:76:9)
at file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:613:90
at Store.transaction (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/store.mjs:213:22)
at Broker.recordTask (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:610:24)
at record (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/src/sync.mjs:169:16)
at reconcile (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/src/sync.mjs:264:3) {
code: 'invalid-request'
}
test at packages/tasks/tests/comment-restart.test.mjs:87:1
✖ initial cursor accepts a comment within the stated one-minute clock skew margin (8.950959ms)
Error: invalid-request
at fail (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:76:9)
at file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:613:90
at Store.transaction (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/store.mjs:213:22)
at Broker.recordTask (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:610:24)
at record (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/src/sync.mjs:169:16)
at reconcile (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/src/sync.mjs:264:3) {
code: 'invalid-request'
}
@@ -0,0 +1,32 @@
✔ stopped bus resumes an empty task cursor after a long outage, exactly once (28.64484ms)
✔ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (23.666868ms)
✔ commit followed by crash before cache advance neither loses nor duplicates a comment (14.221301ms)
✔ failed event insert rolls back cursor and a retry still emits the comment once (12.560194ms)
✖ initial cursor accepts a comment within the stated one-minute clock skew margin (9.83601ms)
ℹ tests 5
ℹ suites 0
ℹ pass 4
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 157.825837
✖ failing tests:
test at packages/tasks/tests/comment-restart.test.mjs:87:1
✖ initial cursor accepts a comment within the stated one-minute clock skew margin (9.83601ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:96: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: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,32 @@
✖ stopped bus resumes an empty task cursor after a long outage, exactly once (27.250983ms)
✔ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (22.74315ms)
✔ commit followed by crash before cache advance neither loses nor duplicates a comment (14.223463ms)
✔ failed event insert rolls back cursor and a retry still emits the comment once (12.305807ms)
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (10.000772ms)
ℹ tests 5
ℹ suites 0
ℹ pass 4
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 155.212154
✖ failing tests:
test at packages/tasks/tests/comment-restart.test.mjs:17:1
✖ stopped bus resumes an empty task cursor after a long outage, exactly once (27.250983ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:24:10)
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: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,43 @@
✔ comment cursors isolate businesses, move only forward and remain append-only (10.284426ms)
✖ only trusted sync writes validated cursors; events and cursor share rollback (7.210591ms)
ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 70.431667
✖ failing tests:
test at packages/bus/tests/comment-cursors.test.mjs:37:1
✖ only trusted sync writes validated cursors; events and cursor share rollback (7.210591ms)
AssertionError [ERR_ASSERTION]: The validation function is expected to return "true". Received false
Caught error:
Error: CHECK constraint failed: task_id > 0
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-cursors.test.mjs:43:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: Error: CHECK constraint failed: task_id > 0
at Store.run (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/store.mjs:192:34)
at file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:614:21
at Store.transaction (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/store.mjs:213:22)
at Broker.recordTask (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/broker.mjs:610:24)
at record (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-cursors.test.mjs:18:64)
at assert.throws.record.task_id (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-cursors.test.mjs:43:25)
at getActual (node:assert:580:5)
at strict.throws (node:assert:728:24)
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-cursors.test.mjs:43:12)
at Test.runInAsyncScope (node:async_hooks:226:14),
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,29 @@
✔ comment cursors isolate businesses, move only forward and remain append-only (10.674228ms)
✖ only trusted sync writes validated cursors; events and cursor share rollback (7.31472ms)
ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 70.371053
✖ failing tests:
test at packages/bus/tests/comment-cursors.test.mjs:37:1
✖ only trusted sync writes validated cursors; events and cursor share rollback (7.31472ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-cursors.test.mjs:45:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,38 @@
✖ comment cursors isolate businesses, move only forward and remain append-only (11.527912ms)
✔ only trusted sync writes validated cursors; events and cursor share rollback (7.435787ms)
ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 70.128434
✖ failing tests:
test at packages/bus/tests/comment-cursors.test.mjs:21:1
✖ comment cursors isolate businesses, move only forward and remain append-only (11.527912ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
{
+ last_comment: 15,
- last_comment: 2,
task_id: 7
}
]
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-cursors.test.mjs:27:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [ { task_id: 7, last_comment: 15 } ],
expected: [ { task_id: 7, last_comment: 2 } ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,31 @@
✖ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (32.552296ms)
✔ migration refuses active writers and drifted legacy schemas, with no partial upgrade (60.748386ms)
✔ named migration registry chains 3b through 3c to a fixture-only future version in order (37.060975ms)
✔ migration refuses mismatched source metadata without changing schema or evidence (29.074581ms)
✔ offline command migrates only the supplied fixture root and reports usage errors (95.244116ms)
ℹ tests 5
ℹ suites 0
ℹ pass 4
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 303.209541
✖ failing tests:
test at packages/bus/tests/comment-migration.test.mjs:31:1
✖ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (32.552296ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-migration.test.mjs:34:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /schema-mismatch/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,33 @@
✔ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (39.540678ms)
✔ migration refuses active writers and drifted legacy schemas, with no partial upgrade (57.244122ms)
✔ named migration registry chains 3b through 3c to a fixture-only future version in order (36.896082ms)
✖ migration refuses mismatched source metadata without changing schema or evidence (31.467615ms)
✔ offline command migrates only the supplied fixture root and reports usage errors (102.735849ms)
ℹ tests 5
ℹ suites 0
ℹ pass 4
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 319.923127
✖ failing tests:
test at packages/bus/tests/comment-migration.test.mjs:94:1
✖ migration refuses mismatched source metadata without changing schema or evidence (31.467615ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-migration.test.mjs:99:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /schema-mismatch/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,23 @@
✔ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (37.342944ms)
✔ migration refuses active writers and drifted legacy schemas, with no partial upgrade (56.790949ms)
✖ named migration registry chains 3b through 3c to a fixture-only future version in order (35.878789ms)
✔ migration refuses mismatched source metadata without changing schema or evidence (29.134341ms)
✔ offline command migrates only the supplied fixture root and reports usage errors (103.552105ms)
ℹ tests 5
ℹ suites 0
ℹ pass 4
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 310.603264
✖ failing tests:
test at packages/bus/tests/comment-migration.test.mjs:65:1
✖ named migration registry chains 3b through 3c to a fixture-only future version in order (35.878789ms)
Error: schema-mismatch
at new Store (file:///mnt/storage/scratch/tmp/comment-migration-0xrMeg/future-bus/src/store.mjs:145:15)
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-migration.test.mjs:85:18)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
@@ -0,0 +1,31 @@
✖ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (34.369883ms)
✔ migration refuses active writers and drifted legacy schemas, with no partial upgrade (56.180221ms)
✖ named migration registry chains 3b through 3c to a fixture-only future version in order (35.04965ms)
✔ migration refuses mismatched source metadata without changing schema or evidence (29.700367ms)
✔ offline command migrates only the supplied fixture root and reports usage errors (102.526376ms)
ℹ tests 5
ℹ suites 0
ℹ pass 3
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 308.691614
✖ failing tests:
test at packages/bus/tests/comment-migration.test.mjs:31:1
✖ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (34.369883ms)
TypeError: Cannot read properties of undefined (reading 'body')
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-migration.test.mjs:37:73)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
test at packages/bus/tests/comment-migration.test.mjs:65:1
✖ named migration registry chains 3b through 3c to a fixture-only future version in order (35.04965ms)
TypeError: Cannot read properties of undefined (reading 'id')
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-migration.test.mjs:89:51)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
@@ -0,0 +1,106 @@
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
file:///mnt/storage/src/mosaic-stack-rocko-49/scripts/land-work/read.mjs:33
throw new Refusal(reason, detail);
^
Refusal [Error]: meta says version 3c, digest d7b8a781f2ac
at refuse (file:///mnt/storage/src/mosaic-stack-rocko-49/scripts/land-work/read.mjs:33:9)
at checkSchema (file:///mnt/storage/src/mosaic-stack-rocko-49/scripts/land-work/read.mjs:82:5)
at openBus (file:///mnt/storage/src/mosaic-stack-rocko-49/scripts/land-work/read.mjs:55:5)
at file:///mnt/storage/src/mosaic-stack-rocko-49/[eval1]:3:12
at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
at async node:internal/modules/esm/loader:226:26
at async ModuleLoader.executeModuleJob (node:internal/modules/esm/loader:223:20)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {
reason: 'bus-schema'
}
Node.js v26.8.1
FAIL openBus opens the database read-only
FAIL other-business: exit 2, wanted 2 decision-not-found: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL no-raise: exit 2, wanted 2 decision-not-found: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL not-a-merge: exit 2, wanted 2 not-a-merge: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL open: exit 2, wanted 2 not-resolved: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL withdrawn: exit 2, wanted 2 not-resolved: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL by-agent: exit 2, wanted 2 not-human: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL via-webui: exit 2, wanted 2 not-human: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL no-answer: exit 2, wanted 2 not-human: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL answer-role: exit 2, wanted 2 not-human: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL answer-run: exit 2, wanted 2 not-human: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL answer-choice: exit 2, wanted 2 not-human: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL two-answers: exit 2, wanted 2 not-human: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL chose-no: exit 2, wanted 2 not-merge-option: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL two-refs: exit 2, wanted 2 work-ref: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL bad-ref: exit 2, wanted 2 work-ref: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL task-ref-disagrees: exit 2, wanted 2 work-ref: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL raise-task-disagrees: exit 2, wanted 2 work-ref: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL target-main: exit 2, wanted 2 target-branch: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL superseded-question: exit 2, wanted 2 superseded: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL superseded-task: exit 2, wanted 2 superseded: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL superseded-by: exit 2, wanted 2 superseded: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL no-verdict: exit 2, wanted 2 no-verdict: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL changes-later: exit 2, wanted 2 not-approved: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL short-commit: exit 2, wanted 2 not-approved: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL wrong-task-body: exit 2, wanted 2 not-approved: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL args: exit 2, wanted 2 decision-not-found: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL earlier-merge: exit 2, wanted 0: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL ref-moved: exit 2, wanted 2 ref-moved: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL root-top: exit 2, wanted 2 root-file: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL root-newdir: exit 2, wanted 2 root-file: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL root-src: exit 2, wanted 2 root-file: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL unrelated: exit 2, wanted 2 base-conflict: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL empty: exit 2, wanted 2 base-conflict: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL an empty reviewed diff says so
FAIL dirty-changed: exit 2, wanted 2 checkout-dirty: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL dirty-staged: exit 2, wanted 2 checkout-dirty: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL dirty-untracked: exit 2, wanted 2 checkout-dirty: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL conflict: exit 2, wanted 2 base-conflict: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
OK a refusal after the worktree is added leaves no worktree or scratch
OK cleanup looks for the worktree's Docker network
FAIL dirty-during: exit 2, wanted 2 checkout-dirty: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
OK a reviewed path dirtied during the gate moves no ref
FAIL drift: exit 2, wanted 2 patch-drift: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
OK patch-drift leaves no worktree or scratch
FAIL gate-pkg: exit 2, wanted 2 gate-failed: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL gate-failed names the failing step's log, and it exists
OK gate-failed leaves no worktree or scratch
FAIL gate-script: exit 2, wanted 2 gate-failed: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL a failing scripts/test-*.sh fails the gate
OK a failed gate moves no ref
FAIL nomods: exit 2, wanted 4: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
OK a target without node_modules leaves no worktree or scratch
FAIL moved: exit 2, wanted 2 base-moved: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL base-moved keeps the base where it moved to
FAIL dry: exit 2, wanted 0: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
OK --dry-run writes no ref
FAIL --dry-run prints the commit with --subject and --attribution
FAIL --dry-run runs the gate
OK --dry-run leaves no worktree or scratch
FAIL land: exit 2, wanted 0: land-work: refused: bus-schema meta says version 3c, digest d7b8a781f2ac: bus-schema meta says version 3c, digest d7b8a781f2ac
FAIL the landing is one commit on the old base
fatal: bad object d37df7db28eb71d11b670275b506e2226e65436e
OK its diff is the reviewed diff by patch-id
FAIL its tree is the dry run's
fatal: bad object d37df7db28eb71d11b670275b506e2226e65436e
FAIL the message has the option's subject, the citations and the attribution last
FAIL it prints the line for the PM
FAIL 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
grep: /gate.log: No such file or directory
FAIL 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: 26 passed, 58 failed
@@ -0,0 +1,28 @@
✖ comment cursors isolate businesses, move only forward and remain append-only (11.078382ms)
✔ only trusted sync writes validated cursors; events and cursor share rollback (7.151096ms)
ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 69.411141
✖ failing tests:
test at packages/bus/tests/comment-cursors.test.mjs:21:1
✖ comment cursors isolate businesses, move only forward and remain append-only (11.078382ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-cursors.test.mjs:33:13)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /append-only/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,28 @@
✖ comment cursors isolate businesses, move only forward and remain append-only (11.405908ms)
✔ only trusted sync writes validated cursors; events and cursor share rollback (7.068233ms)
ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 67.73242
✖ failing tests:
test at packages/bus/tests/comment-cursors.test.mjs:21:1
✖ comment cursors isolate businesses, move only forward and remain append-only (11.405908ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-cursors.test.mjs:28:47)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /must advance/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,33 @@
✔ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (45.090871ms)
✖ migration refuses active writers and drifted legacy schemas, with no partial upgrade (60.538001ms)
✔ named migration registry chains 3b through 3c to a fixture-only future version in order (37.422878ms)
✔ migration refuses mismatched source metadata without changing schema or evidence (31.85347ms)
✔ offline command migrates only the supplied fixture root and reports usage errors (103.245046ms)
ℹ tests 5
ℹ suites 0
ℹ pass 4
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 325.810621
✖ failing tests:
test at packages/bus/tests/comment-migration.test.mjs:49:1
✖ migration refuses active writers and drifted legacy schemas, with no partial upgrade (60.538001ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-migration.test.mjs:62:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /writer-locked/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,33 @@
✔ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (37.363022ms)
✔ migration refuses active writers and drifted legacy schemas, with no partial upgrade (58.699075ms)
✔ named migration registry chains 3b through 3c to a fixture-only future version in order (37.858983ms)
✔ migration refuses mismatched source metadata without changing schema or evidence (30.150316ms)
✖ offline command migrates only the supplied fixture root and reports usage errors (62.20026ms)
ℹ tests 5
ℹ suites 0
ℹ pass 4
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 274.872082
✖ failing tests:
test at packages/bus/tests/comment-migration.test.mjs:106:1
✖ offline command migrates only the supplied fixture root and reports usage errors (62.20026ms)
AssertionError [ERR_ASSERTION]: migration refused: schema-mismatch
1 !== 0
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-migration.test.mjs:111: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: 1,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,142 @@
[
{
"id": "01-restore-durable-ids",
"path": "packages/tasks/src/adapter.mjs",
"test": "packages/tasks/tests/comment-restart.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "02-persist-observed-ids",
"path": "packages/bus/src/broker.mjs",
"test": "packages/tasks/tests/comment-restart.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "03-startup-comment-read",
"path": "packages/tasks/src/sync.mjs",
"test": "packages/tasks/tests/comment-restart.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "04-exclude-bots",
"path": "packages/tasks/src/sync.mjs",
"test": "packages/tasks/tests/comment-restart.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "05-strict-new-id",
"path": "packages/tasks/src/sync.mjs",
"test": "packages/tasks/tests/comment-restart.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "06-cache-after-commit",
"path": "packages/tasks/src/sync.mjs",
"test": "packages/tasks/tests/comment-restart.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "07-event-cursor-atomicity",
"path": "packages/tasks/src/sync.mjs",
"test": "packages/tasks/tests/comment-restart.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "08-no-empty-poll-row",
"path": "packages/tasks/src/sync.mjs",
"test": "packages/tasks/tests/comment-restart.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "09-initial-clock-margin",
"path": "packages/tasks/src/sync.mjs",
"test": "packages/tasks/tests/comment-restart.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "10-durable-initial-epoch",
"path": "packages/tasks/src/adapter.mjs",
"test": "packages/tasks/tests/comment-restart.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "11-validate-cursor-input",
"path": "packages/bus/src/broker.mjs",
"test": "packages/bus/tests/comment-cursors.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "12-sync-only-cursor-write",
"path": "packages/bus/src/broker.mjs",
"test": "packages/bus/tests/comment-cursors.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "13-business-isolation",
"path": "packages/bus/src/broker.mjs",
"test": "packages/bus/tests/comment-cursors.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "14-explicit-offline-only",
"path": "packages/bus/src/store.mjs",
"test": "packages/bus/tests/comment-migration.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "15-pinned-source-metadata",
"path": "packages/bus/src/store.mjs",
"test": "packages/bus/tests/comment-migration.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "16-chain-all-steps",
"path": "packages/bus/src/store.mjs",
"test": "packages/bus/tests/comment-migration.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "17-preserve-source-copy",
"path": "packages/bus/src/store.mjs",
"test": "packages/bus/tests/comment-migration.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "18-land-work-version",
"path": "scripts/land-work/read.mjs",
"test": "scripts/test-land-work.sh",
"result": "KILLED",
"exit": 1
},
{
"id": "21-exclusive-writer-lock",
"path": "packages/bus/src/store.mjs",
"test": "packages/bus/tests/comment-migration.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "22-migration-command",
"path": "packages/bus/src/migrate-schema.mjs",
"test": "packages/bus/tests/comment-migration.test.mjs",
"result": "KILLED",
"exit": 1
}
]
@@ -0,0 +1,16 @@
[
{
"id": "19-append-only-cursor-evidence",
"path": "packages/bus/schema.sql + matching migration DDL",
"test": "packages/bus/tests/comment-cursors.test.mjs",
"result": "KILLED",
"exit": 1
},
{
"id": "20-monotonic-cursor",
"path": "packages/bus/schema.sql + matching migration DDL",
"test": "packages/bus/tests/comment-cursors.test.mjs",
"result": "KILLED",
"exit": 1
}
]
@@ -0,0 +1,71 @@
c65046eb1b2420a87ea17bdf2118d6a4dafebc3195eb1f002f9ba00ce24a9730 BUILD.md
b753f630301bf125e9e286b4156d0736930dd8fb9cd8eb2ebf316688f9191a07 DDL.sql
a881909e64eb8d9eb142d069098b2490c9cd6b8142a56457c5bfe2134d0cf600 HANDOFF.md
5839f2df034baae6f980e81111f006ece4367232cec519e073bd264456e8f37e MIGRATION.md
e693ff8400534fae44e475ce1422390ce6598533dab697480165518fb532c50e MUTANTS.md
41d4ac89273398fb4677b9341a1d8d677f4d7f92bd8ab44752f8bafff5535bab base.txt
35546194585146904441e06b6fe3ba87cead7464f2a570a632cfb4b404415a04 build.patch
ba45d57c9b255311bb220598f90c1a4fb01b420511974c28f102881cbd7df21a busy-before-mutants.log
ba45d57c9b255311bb220598f90c1a4fb01b420511974c28f102881cbd7df21a busy-check.log
f96467d1db869496c274d0ac92284130f38f60af074af0580eb423eff30355d6 busy.py
87ef1321a1a08be64a20499889fc24b5549b47d6d33abfd86db95f62fb3cb6d0 candidate-manifest.sha256
3e2f603acf5c007b96b0dae6e37985a9e7e966015f4db961cc7ddcf43053e2c8 gate/pkg-bus.log
ee78575d3f14f385f5a85d47d62f53b127bb053782b3386aa632ce5c823a4247 gate/pkg-cli.log
82b4a1e4bdc90c09115504f74b4ffff76c9c1a498a18d65137df11841343e4d0 gate/pkg-harness.log
5daf1b9018f053eede5e32a3a75d18426846a7d9a6a3f8ee9dfe93cb8048ba79 gate/pkg-seat.log
2eb73f9f9a5303ee5a1eab424ac90690dc40bcc7a3bbaa2a6fd60b2c678b585d gate/pkg-tasks.log
b3f044a8e3d37d8dae19c7f6ea4025625f1292dc95dcad205ba77f339d5dd717 gate/pkg-webui.log
5e2eb19c92025dfd5553e760b05578270396d8814871572cc3d1bc1de5715b8f gate/results.json
8e2254aa3e1bb9b205ab72c68273d447c0328c9f156ccba2a46f3a0476468b58 gate/script-land-work.log
428da18d15f3ca0b42b4f7d2b39d92a6b1a32556bc94f46c70a95ac2b5c42133 gate-copy-failure/pkg-bus.log
e16912290b0dbee3a97b109ab69307a4235147dc38a8fe3ff940cf1deeeaa96a gate-copy-failure/pkg-cli.log
529f978905bd1d655a40c868dd605831e99fb230be21afd3b15d572adb62177c gate-copy-failure/pkg-tasks.log
0bf0dab068f6bb86bd2df9851c99c383ef4218a3081b5d1b905f2784b6213279 gate-copy-failure/results.json
333e2e7b516cf4fd5743f68ee088ee55c0363efdc5f2f949087efeb2e7d7d1b6 gate-setup-failure/pkg-bus.log
7f524659d60b9b6c40460424dce7d70e33cec4a54b63d8e7d8d1c204f7afccab gate-setup-failure/pkg-cli.log
8cd14dabf01294b2ec507df3684f1cc75f91cd37ee4e5c91020aa40a42062877 gate-setup-failure/pkg-tasks.log
0bf0dab068f6bb86bd2df9851c99c383ef4218a3081b5d1b905f2784b6213279 gate-setup-failure/results.json
e3cf413b6529fbd2933d599bd061015a2e66ae300a6e5bd1e8776afc0bd822a3 gate.py
20aee40b264a01a38f600ef03a4ccdf7cbedde6d41c178f9888ac06cf8b005a4 green-adapter.log
832965736cde51e7535efcec29f55f7c09e117c84446bfcf528d9b2ae111bf6d green-comments.log
eb467b8e1609749b5bbb8f58b22fe764936a046ea84388b925f4faec272b6521 green-copy.log
2f02ef5e3e9463bb036adbe0d69e77ebefcc29518fc440a8a45b425f4203f7fd green-cursors.log
e587c2e5d47709c76f5ac404ffdb01de6a3a8d93aa0e77d498faaca88e98d5b7 green-migration.log
c2dd9873f3babad5b7a81e907aa1c81b24b2868cd5353b0dcd662002da17e239 green-schema.log
9591cb77d6d2f842e5499a69c6dd2d8e79163a8caa9aff2ad8e9984774bd1a96 green-sync.log
d6e9b1eeddb2bc05f18ef690948cb815856345c60ab52c1225a86c57e1a69151 import-consumers.txt
4b028a9dc2541914d4e4b961de66b4abca7bfe8afe43b0999cc833d3bd3abef5 live-schema-evidence.txt
4613a426b620a1a2e9207fdb67af48a34fd1e563041e938579b6abede19fa839 mutants/01-restore-durable-ids.log
e438a8e33e654ae686367b1d764f121eb825bfb257b1b9bd79ad73d06ac553e5 mutants/02-persist-observed-ids.log
260dcb4134f0112fea864bd15bf580093e5793cb121306fbdb3b538f5e469a40 mutants/03-startup-comment-read.log
2b5ff04af374084cc33845644c4cdbb953e0a1b21a13135e5021b0d50f2f637d mutants/04-exclude-bots.log
dadff25d58729cd26dcad856c21e038ed84758724a847a92130e98c1a1dd16eb mutants/05-strict-new-id.log
3cbe24b3ecce666d786e6a436ccba1435b88e2a304a8efc521a8ea2e90cdd688 mutants/06-cache-after-commit.log
a82b7aa4590acef6e9af1c748afc7c1847a94d7a368b1a6abdfec6632a995699 mutants/07-event-cursor-atomicity.log
484185b9dd18f0fd7137c6717370d72e2ba20395d2ea0ed94ba038302ed3ea07 mutants/08-no-empty-poll-row.log
9f592a70dd5fc3c2f9e4a5d04fac362373d32d1b1f350c21c3c55a5bf73ac188 mutants/09-initial-clock-margin.log
3721d552fe450c79cac2d409f254afc6fab048b5b94b8379bfc7dd478c7a020b mutants/10-durable-initial-epoch.log
59065844d77a4ee61315d7033fe6b84963076a03862aa6ccedf7735dcf0f544f mutants/11-validate-cursor-input.log
09e131da5363ac4f0811ce55d1ceeda12cf11b13ebb50f94e664667a1eb8d8cb mutants/12-sync-only-cursor-write.log
75d30a0ca429bda8809b00b5376776a5e50dcb97582b6dbcaa9cbebc83c2fe16 mutants/13-business-isolation.log
71f800f47b21e1ed8527da7fe9b8228e0cb86569afebe0f23119d592e24c37fd mutants/14-explicit-offline-only.log
567fcd48fc383d3c3460454afb60a900c6d57ecbc94288d40580fc0a6d8de033 mutants/15-pinned-source-metadata.log
8d88cf03632974857ad3761763c4f2c6f2406691a0dada33ae73cbe874a099a3 mutants/16-chain-all-steps.log
d57c4bc21ade8f9de14b76e97cf4399fa7b961a1c41102b0103627ee1e8a49bd mutants/17-preserve-source-copy.log
7a54737de63d88b702f54ed78b419e1a62a5360ad5ded9108977aa2c2c124da5 mutants/18-land-work-version.log
8da216b35a3812114ae8b5af7798c99e76303e1699e0561f03945231fdd107ca mutants/19-append-only-cursor-evidence.log
d1efb3fdad0d2e093bc20b930a7ef8a7c121cea45b59653be1a6d5c7ae73d29a mutants/20-monotonic-cursor.log
ee88d369aebd9c54fb093580d3f33fee80ac4248035cf8f4d4eb927315b4d317 mutants/21-exclusive-writer-lock.log
6c3233e554756513dd2244441901be1111588a4f96703e4e418988f1f387f3bf mutants/22-migration-command.log
e10e4fc5f8a0536d69708b48ebcbec7d28d4a02fc444ff17155a511ade0ff939 mutants/results.json
c4c2d3967de1ef39127b07bb03ad0976c52b8da1e4d7c17b878d2cf8a08e1c44 mutants/sql-results.json
16796b6a1fcd28c4602187992446e1fdf7ecc20ba3dd732c74d3e818f9be6a98 mutants.py
d777d5f548e9b05cd58615867b7b68980204fc8032c3e07135002b98ac54ea00 prepare.py
37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570 process-sweep.json
43998241cf266f2581366e3b1b9366f2b6fd5dca6a5a243ffc80a6ff622012ae rebuild-manifest.txt
688e6f76d653e3b3b963a34f58be7318071f68296b48be30774afc345f76577a red-comments.log
a9bdff1d3e8a0f94c8b4297dd0eec4611a223f4ddb9cca9b7da1c1942cc9755c red-migration.log
4ddc7f058bdaea97ff65349c94811af6b826a24ce56653ac3eaf4a9428acdf4b restored-comments.log
3c478ec3216185a386636aebf540a0064288e66b66413d4a9941e42c1ad2c586 restored-manifest.txt
9da83b2b48feb2461ece011c78dc7ae06c8b67bcc51d30e2cc532e031fd89e0e scan.json
366cdf3d4bf64018e5a2fc9f1adac5b50eec8c8a654c3bbc67c0a3dc7de20a4d sql-mutants.py
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env python3
import hashlib,pathlib,subprocess
root=pathlib.Path('/mnt/storage/src/mosaic-stack-rocko-49');out=pathlib.Path(__file__).resolve().parent
tracked=subprocess.check_output(['git','diff','--name-only'],cwd=root,text=True).splitlines()
new=subprocess.check_output(['git','ls-files','--others','--exclude-standard','--','packages/bus/src','packages/bus/tests','packages/tasks/tests'],cwd=root,text=True).splitlines()
paths=sorted(set(tracked+new))
assert all(p.startswith(('packages/bus/','packages/tasks/','scripts/land-work/')) for p in paths)
patch=subprocess.check_output(['git','diff','--binary','--no-ext-diff'],cwd=root)
for path in sorted(new):
r=subprocess.run(['git','diff','--no-index','--binary','--','/dev/null',path],cwd=root,stdout=subprocess.PIPE)
assert r.returncode==1
patch+=r.stdout
(out/'build.patch').write_bytes(patch)
(out/'candidate-manifest.sha256').write_text(''.join(hashlib.sha256((root/p).read_bytes()).hexdigest()+' '+p+'\n' for p in paths))
(out/'base.txt').write_bytes(subprocess.check_output(['git','rev-parse','HEAD'],cwd=root))
print('candidate files',len(paths),'manifest sha',hashlib.sha256((out/'candidate-manifest.sha256').read_bytes()).hexdigest())
@@ -0,0 +1 @@
[]
@@ -0,0 +1,17 @@
packages/bus/README.md: OK
packages/bus/schema.sql: OK
packages/bus/src/broker.mjs: OK
packages/bus/src/migrate-schema.mjs: OK
packages/bus/src/migrations/3b-3c.sql: OK
packages/bus/src/migrations/3b.sql: OK
packages/bus/src/store.mjs: OK
packages/bus/tests/comment-cursors.test.mjs: OK
packages/bus/tests/comment-migration.test.mjs: OK
packages/bus/tests/fixtures/schema-3b.sql: OK
packages/bus/tests/schema.test.mjs: OK
packages/tasks/README.md: OK
packages/tasks/src/adapter.mjs: OK
packages/tasks/src/sync.mjs: OK
packages/tasks/tests/comment-restart.test.mjs: OK
packages/tasks/tests/world.mjs: OK
scripts/land-work/read.mjs: OK
@@ -0,0 +1,78 @@
✖ stopped bus resumes an empty task cursor after a long outage, exactly once (31.224532ms)
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (13.150195ms)
✖ commit followed by crash before cache advance neither loses nor duplicates a comment (23.243094ms)
✖ failed event insert rolls back cursor and a retry still emits the comment once (12.860134ms)
ℹ tests 4
ℹ suites 0
ℹ pass 0
ℹ fail 4
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 162.21039
✖ failing tests:
test at packages/tasks/tests/comment-restart.test.mjs:16:1
✖ stopped bus resumes an empty task cursor after a long outage, exactly once (31.224532ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:23:10)
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: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/tasks/tests/comment-restart.test.mjs:30:1
✖ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (13.150195ms)
Error: no such table: task_comment_cursors
at Store.get (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/src/store.mjs:128:21)
at rows (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:35:30)
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:36:16)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
code: 'ERR_SQLITE_ERROR',
errcode: 1,
errstr: 'SQL logic error'
}
test at packages/tasks/tests/comment-restart.test.mjs:60:1
✖ commit followed by crash before cache advance neither loses nor duplicates a comment (23.243094ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
2 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:71: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: 2,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/tasks/tests/comment-restart.test.mjs:74:1
✖ failed event insert rolls back cursor and a retry still emits the comment once (12.860134ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/tasks/tests/comment-restart.test.mjs:81: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: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,28 @@
✖ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (33.45361ms)
✔ migration refuses active writers and drifted legacy schemas, with no partial upgrade (57.816901ms)
ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 140.115716
✖ failing tests:
test at packages/bus/tests/comment-migration.test.mjs:31:1
✖ explicit 3b copy migration preserves evidence and opens as 3c; normal startup never migrates (33.45361ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-49/packages/bus/tests/comment-migration.test.mjs:34:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /schema-mismatch/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,13 @@
✔ stopped bus resumes an empty task cursor after a long outage, exactly once (30.685468ms)
✔ persisted IDs avoid replay, exclude bots and bound rows to observed new comments (23.936732ms)
✔ commit followed by crash before cache advance neither loses nor duplicates a comment (14.604568ms)
✔ failed event insert rolls back cursor and a retry still emits the comment once (12.157324ms)
✔ initial cursor accepts a comment within the stated one-minute clock skew margin (9.925179ms)
ℹ tests 5
ℹ suites 0
ℹ pass 5
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 160.500044
@@ -0,0 +1 @@
17/17 candidate hashes restored after mutants
+5
View File
@@ -0,0 +1,5 @@
{
"files": 70,
"findings": [],
"scope": "packet bytes only; no live config, secrets or database read"
}
@@ -0,0 +1,32 @@
#!/usr/bin/env python3
import hashlib,json,os,pathlib,re,signal,subprocess,sys
root=pathlib.Path('/mnt/storage/src/mosaic-stack-rocko-49')
out=pathlib.Path(__file__).resolve().parent/'mutants'
paths=['packages/bus/schema.sql','packages/bus/src/migrations/3b-3c.sql','packages/bus/src/store.mjs']
original={path:(root/path).read_bytes() for path in paths}
results=[]
for id,pattern in [
('19-append-only-cursor-evidence',r'CREATE TRIGGER task_comment_cursors_no_(?:update|delete|replace)\b.*?END;\n'),
('20-monotonic-cursor',r'CREATE TRIGGER task_comment_cursors_forward\b.*?END;\n'),
]:
try:
for path in paths[:2]:
text=original[path].decode();text,n=re.subn(pattern,'',text,flags=re.S)
assert n==(3 if id.startswith('19') else 1)
(root/path).write_text(text)
sha=hashlib.sha256((root/paths[0]).read_bytes()).hexdigest()
(root/paths[2]).write_text(re.sub(r"export const SCHEMA_SHA256 = '[a-f0-9]+';",f"export const SCHEMA_SHA256 = '{sha}';",original[paths[2]].decode()))
with (out/(id+'.log')).open('w') as log:
p=subprocess.Popen(['node','--test','packages/bus/tests/comment-cursors.test.mjs'],cwd=root,
env={**os.environ,'DOCKER_HOST':'unix:///nonexistent.sock'},stdout=log,stderr=subprocess.STDOUT,start_new_session=True)
try:rc=p.wait(timeout=90)
except subprocess.TimeoutExpired:os.killpg(p.pid,signal.SIGKILL);p.wait();rc='timeout'
content=(out/(id+'.log')).read_text()
invalid=any(x in content for x in ['SyntaxError:', 'ERR_MODULE_NOT_FOUND','schema-source-mismatch','migration-target-mismatch'])
verdict='INVALID' if invalid else 'TIMEOUT' if rc=='timeout' else 'SURVIVED' if rc==0 else 'KILLED'
results.append(dict(id=id,path='packages/bus/schema.sql + matching migration DDL',test='packages/bus/tests/comment-cursors.test.mjs',result=verdict,exit=rc))
print(id,verdict,rc,flush=True)
finally:
for path,data in original.items():(root/path).write_bytes(data)
(out/'sql-results.json').write_text(json.dumps(results,indent=2)+'\n')
sys.exit(0 if all(r['result']=='KILLED' for r in results) else 1)
+1
View File
@@ -984,3 +984,4 @@ are never rewritten or removed; corrections are new entries.
2026-10-15 15:55:15 UTC | Sage (Opus 5.5, 1ef1e4f8) | decision 78 ready | Jason via Mos sage-0204 (Q161): "D4 yes"; recorded as lead decision 146 in be9377a91: ready = row 42's acceptance run passes with Jason on the live mosaic-stack business; merge plan starts then, step 4 merge stays Jason's
2026-10-15 15:58:40 UTC | Sage (Opus 5.5, 1ef1e4f8) | launch ready rows | Mos sage-0205: rows 121 #1615 (darkwing, design first), 123 #1617 (researcher), 131 #1626 (dewey) launched in sage-0291 to 0293; row 49 #1535 reassigned sage to rocko (rev 1268, queue ffb7d3f5f) and launched in sage-0294; row 99 stays blocked with its recorded reason (Mos decision 133)
2026-10-15 16:20:14 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 49 #1535 comment cursor across restarts r1 | built on 7b166a0f1; explicit 3b→3c named migration, durable IDs/epoch, atomic events/cursors, no empty-poll rows; gate bus178 tasks97 cli195 webui112 harness84 seat132 land-work84 green; 22/22 mutants killed; 17/17 rebuild/restored hashes, sweep empty, slot clear16:18:57; packet agents/rocko/work/queue-49/r1 manifest87ef1321; in-review1276/requestfailed1277 exit1 missing token, Sage posts; no live action
2026-10-15 16:21:33 UTC | Sage (Opus 5.5, 1ef1e4f8) | row 121 design, slot, row 49 review | row 121 #1615 design approved with five changes in 28556 (delivery_same_thread scoped to the thread's instance; threadIdleSeconds optional, default 900; one-assistant refusal; id pattern on store path; chief chain refusal text), sage-0308; queue revs 1269-1275 committed cf12c25e9, which also swept Rocko's 1276-1277; slot Rocko to Dewey (sage-0309, sage-0310); row 49 review request posted as jarvis, 28557, revs 1278-1279 uncommitted (Dewey holds the slot); Researcher sent row 49 r1 for the 3c to 3d rebase (sage-0311); Rocko packet r1 committed after PACKET_OK and a clean secrets scan (candidate 87ef1321, packet a37384d8) | done