agents: Rocko row 122 round 1 candidate and verification packet

This commit is contained in:
2026-10-14 20:12:48 -05:00
parent 33acefdce8
commit 85540a74e8
76 changed files with 6317 additions and 0 deletions
+157
View File
@@ -0,0 +1,157 @@
# Row 122 design — agent settings and organization writes (#1616)
Design first, for Sage to post before implementation. Rocko, GPT-6.1-Sol.
Brief: docs/plans/2026-10-14_prd-1.2-build.md, pinned 240d25518,
section Agent limits as settings, with the organization-file write path.
Requirements: REQ-ROLE-3, REQ-LIMIT-1, REQ-LAUNCH-1, REQ-WEB-1.
No live organization/configuration/secrets were read. No product code or
migration has run. Decision144 leaves Jason's file to Jason.
## Proposed schema
Keep businessVersion1, launch.by and launch.instances. Replace launch.max
with launch.models and launch.limits. Example defaults, with provider/model
values explicitly chosen by the operator rather than inferred:
```json
{
"launch": {
"by": "pm",
"instances": ["coder", "reviewer", "cto"],
"models": {
"opus": {"provider": "anthropic", "model": "claude-opus-5-5"},
"sonnet": {"provider": "anthropic", "model": "claude-sonnet-5-5"}
},
"limits": {
"models": {
"anthropic": {"claude-opus-5-5": 4, "claude-sonnet-5-5": 4}
}
}
}
}
```
Model-table keys are stable configuration ids; each entry requires a provider
and an exact external model string. Each role's vars.model selects a table
key, for example pm -> opus, coder/reviewer -> sonnet. Harness selection
stays where it is today; row65 later converts harnesses through the loader.
The table's provider/model override the provider/model used to launch that
instance, and are recorded in its manifest, running-session registry and
launch log. No provider allowlist or provider module is introduced here.
Unknown references fail closed; matching a family substring disappears.
limits has only these optional keys:
- total: count across all running sessions in the organization.
- providers: object mapping exact provider names to counts.
- models: object mapping provider names to objects mapping exact external
model names to counts.
An absent key means unlimited. Empty objects are valid, and an empty limits
object means no concurrency limits. null, fractions, negatives and strings
refuse; accepted counts are nonnegative safe integers, with no configured
upper bound beyond lossless JSON integer representation. 0 stops a new
launch in that scope. Four and four are editable starter/migration values,
not a validator ceiling or a value injected when a key is absent. Removing
an entry explicitly unsets that limit. Aliases for the same provider/model
share the same counter, so a second table key cannot bypass a model limit.
Provider/model limits need not have a currently selected role: they can be
configured before changing an instance's model.
## Migration and Jason's exact change
`mosaic limits migrate --business ID --models FILE` prints the converted
whole document by default. Only explicit --write by a verified human uses
the write path. FILE supplies the model table and the exact mapping from
old external model strings to table keys. No provider inference, substring
routing or automatic write on read/start. Unknown/ambiguous mappings refuse
with the unchanged input. Repeating on an already migrated valid document
is idempotent.
For old launch.max = {opus:4,sonnet:4}, map each family to one explicitly
supplied table key and translate those values to the two corresponding
provider/model limits. Preserve launch.by, launch.instances, harnesses,
credentials, role definitions and unrelated document values. Convert each
role's effective old model selection to its mapped table key; use explicit
instance overrides where a selection was inherited. Do not alter a shared
system execution setting. Require mappings for launch.by and every
launchable instance. Do not invent a total8 or provider8 limit: the old
shape had neither. Legacy family limits that require multiple exact models
per family cannot be faithfully split; refuse and require an operator's
explicit new limits rather than silently widening capacity.
The example above illustrates the exact shape, not a claim about Jason's
provider, ids or instances. The fixture migration produces the replacement
launch block plus per-instance vars.model changes. The build packet will
include that exact JSON patch and command for Jason, with provider/model
mapping explicitly supplied and reviewed through Sage/Mos. It will not
assert that anthropic is Jason's provider or execute a live migration. The
new validator refuses an unmigrated launch.max with a migration instruction.
Re-pin therefore follows Jason's applied migration and Mos's go; the old
host must not launch between schema migration and the coordinated re-pin.
## One write path, fail closed
A trusted host module owns writes to the resolved businessFilePath for a
validated organization id. It accepts an authenticated actor supplied by
the human channel, never an actor string from a browser/CLI payload.
Limits endpoints may change only launch limits/model-table entries and
role model selection. The shared internal module can validate a whole-file
replacement for row123's later chief-of-staff verb; this row grants no
chief-of-staff writer and exposes no unrestricted browser file write.
Serialize writes per organization with a filesystem lock shared by writers;
compare a submitted revision/hash against the current bytes to refuse lost
updates. Read the current regular file without following a symlink, validate
the entire proposed document using the existing role-aware validator, then
prepare same-directory temporary bytes. Preserve an immutable previous
version and an append-only actor/change record with field paths, old/new
values and before/after hashes. Fsync evidence and candidate before atomic
rename, then fsync the directory. Journal prepared/committed outcomes and
recover an interrupted transaction by hashes; an uncertain outcome refuses
new writes until reconciled. Invalid or stale proposals never replace the
old file. Success is reported only after the committed receipt. No secret
contents are logged: unrelated credential references remain references.
CLI and Settings use the same human-only read/update operation behind the
existing human proof. No direct CLI bypass. CLI stays out of transport.mjs;
Settings POST repeats the existing enabled-send, named-host, authenticated
header, exact Origin, same-origin Fetch Metadata, body-size and strict-key
checks. Both submit a revision and receive current normalized settings and
counts; no retry on unknown outcome. GET remains available through the
existing read path. Form inputs distinguish unset from0, show all three
limit kinds and every instance's table selection, and report refusals.
## Launch behavior and acceptance evidence
Within the launcher's existing serialized launch queue, re-read and validate
current limits and model selections for every attempt, including host-start
launches. A missing/invalid file refuses; never fall back to cached limits.
Check total, then provider, then exact provider/model counters, counting the
launcher itself and all still-running registry entries. A launch at the
limit refuses because it would exceed it; unset imposes no check. Lowering
a limit leaves existing sessions alive. Their recorded provider/model tuple
continues to count even after their instance is assigned a different model.
Each refusal logs the hit scope, configured value and running count; each
launch logs harness/provider/model. Existing authority/credential/sandbox
checks remain, and unrelated permission changes still follow their existing
reload/re-pin rules.
Builder tests cover whole-file validation, atomic publication, previous
bytes, actor/old/new receipts and crash/stale-write refusal; every limit
kind unset/0/at/over; live re-read and unchanged existing sessions; exact
model routing; fixture-only print/write migration; human-only CLI; form
send checks. One killed mutant per acceptance check, listed in the packet.
Targeted builder gate and mutants need Sage's slot. No product code starts
until Sage posts this design note on #1616. Landing public/ changes awaits
Mos (decision123); any live step still requires re-pin (decision109).
## Superseding migration ruling — decisions 145 and sage-0254
The earlier optional write/local helper description is superseded: migration
is print-only, has no --write and uses no organization writer or human proof.
Only broker-backed `mosaic limits` and Settings write. Jason installs the output
himself in Mos's window with the PM run idle and the re-pin in that same window.
Both exact Claude table entries use provider `anthropic`, per Mos via Sage
sage-0254. Map pm, cto and reviewer to opus; coder to sonnet. Keep exact model
limits four/four, no total or provider limit, and leave execution.provider alone.
+123
View File
@@ -0,0 +1,123 @@
# Row 122 / #1616 — Agent limits as settings
Base: f529cf485c8a0ac214076052cb8e16f14864c7a0 (row130 included). Candidate
code is uncommitted in detached worktree `/mnt/storage/src/mosaic-stack-rocko-122`.
Sage posted design note as #1616 comment28516. Decision145 supersedes its
optional migration write: migration is print-only, and has no write helper.
## Behavior and authority
`launch.models` is an explicit selection-key → provider/external-model table.
`launch.limits` has optional total, provider and exact provider/model counts.
Absence is unlimited, zero denies new launches, safe whole numbers have no
policy ceiling. The example and Jason's migration use editable four/four model
limits, not fallback limits inserted by code. Roles select table keys through
vars.model; harness selection and the shared execution.provider stay unchanged.
Other provider names route/count independently; aliases share exact tuple counts.
One organization writer validates the complete proposed document, checks the
expected SHA-256 revision, saves exact previous bytes, fsyncs and atomically
renames a candidate, and logs actor/hashes/field old/new values with prepared
and committed receipts. A concurrent writer, unsafe/symlink file, stale revision,
invalid document or uncertain receipt refuses. Host-start recovery can finish
or abort a prepared receipt by hashes. A stale writer lock refuses until an
operator verifies the old writer is gone; no automatic stale-lock deletion race.
The broker exposes human-only limits / limits.set using the existing proof.
Role, reader and tester capabilities cannot write or forge the actor. CLI limits
and Console Settings both go through that broker path. Console POST Settings
requires send mode, named Host, verified identity, exact Origin, same-origin
Fetch Metadata, bounded JSON and strict writable keys. No automatic retry.
The form shows every configured limit, model table and role selection (held CTO
included), keeps a failed draft and explicitly offers re-read. Existing GETs
and preview remain reads. Launcher reloads policy on every serialized attempt
and again after asynchronous checkout preparation; lowering limits keeps existing
sessions, whose recorded exact tuple continues counting. Refusal logs name the
hit; launch logs name harness/provider/model.
## Migration and cut-over
See MIGRATION.md, migration-map.json and jason-migration.patch.json. Provider
facts come from Mos via Sage sage-0254: both Claude entries use anthropic.
The pure CLI prints the converted whole document without bus/config access,
refuses unknown/ambiguous mappings, prints migrated input idempotently and never
writes its input. The packet maps pm, cto and reviewer to opus; coder to sonnet.
Only the two exact model limits four/four are added. No live file was read.
Jason's edit and the re-pin happen in one window Mos runs, with the PM run idle.
Settings public assets require Mos's landing go (decision123). No bus/Console
unit, live organization file or shared wrapper was changed. Only a separate
re-pin can make this candidate live (decision109).
## Targeted builder gate (decision136)
Changed packages: business, bus, cli, harness, seat, webui. Importers of those
packages in current source: control-board, conversation, ledger, tasks. Run all
node test files in those ten packages; ledger is run directly despite lacking
package.json. Browser form coverage is part of webui, including the new
settings-form.test.mjs. Gate serializes files (`--test-concurrency=1`).
Exact affected shell readers:
- scripts/test-queue.sh syntax-checks and executes changed scripts/mosaic.
- scripts/test-task.sh invokes scripts/mosaic-task.mjs, which statically imports
BusinessError/validateRoleDocument from changed business/src/index.mjs and
transitively the changed validator and vocabulary.
scripts/test-discord.sh is an additional regression run for the existing
connector command surface; it is not claimed as a direct reader of the patch.
The other test-*.sh read unchanged config/auth/release/foundation/extension/
conductor/landing paths or scratch fixture trees, not the changed files.
DOCKER_HOST=unix:///nonexistent.sock throughout normal runs. Docker-required
cases will be separately rerun with DOCKER_HOST unset if needed and recorded;
no daemon/socket/volume changes. Slot granted by Sage sage-0259. Each mutant
uses its own process group and whole-group SIGKILL on timeout; a timeout is not
a kill. One recurrence of #258/#261/#262/#263 is noted; a second stops the gate.
## Verification results
Final broad targeted gate passed all 13 steps. The late durability and migration
audit changes were followed by complete business, bus and CLI reruns; those
passing reruns replace their earlier logs in gate/results.json. The bus count
increased by one for an integration check through runtime's real shared writer.
All node cases below passed, with zero skips/failures.
| Node suite | Passed |
| --- | ---: |
| business | 75 |
| bus | 158 |
| seat | 132 |
| harness | 81 |
| cli | 194 |
| webui (browser form included) | 112 |
| control-board | 124 |
| conversation | 182 |
| ledger (direct) | 86 |
| tasks | 92 |
Shell: queue 28 plus its 156 node tests; Discord 78 plus 183 node tests;
task 98. Queue intentionally omits only the canonical-root verify/render checks
in the detached worktree. Task ran with DOCKER_HOST unset and Docker server
29.7.2; all 98 cases ran, so no Docker branches were skipped. Queue, Discord,
all node suites and mutants used DOCKER_HOST=unix:///nonexistent.sock.
The worktree's mosaic-stack-rocko-122_default network existed after Docker
and was removed successfully; no daemon/socket/volume changes.
Initial failures were fixture/expectation updates, not known flakes: harness's
probe-model needed a table entry; shell.test's exact single-POST assertion
needed the explicitly authorized Settings POST. First failure logs are retained
under gate/*-before-*.log. The new runtime integration fixture initially lacked
its disposable data directory, fixed before the final bus rerun. No recurrence
of #258/#261/#262/#263 occurred.
All 33 mutants were killed, with no syntax/import-only kills, survivors or
timeouts. See MUTANTS.md and mutants/results.json for individual sites, commands
and logs. Every mutant used a separate process group; source was restored and
all 43 candidate hashes were checked afterwards (manifest-check.txt). The
post-run cwd/path-scoped node/chromium sweep found no runners before or after
(sweep.log). Slot returned to Sage at 2026-10-15 01:04:27 UTC.
Candidate manifest SHA-256:
667441df2411db4c085ec6981d8c9c5f55578debc02a389a2f4df9283df775ae.
@@ -0,0 +1,26 @@
# Jason’s print-only migration (decision 145)
Mapping facts: Sage sage-0252 and Mos via Sage sage-0254. Anthropic serves both
Claude models directly; the system execution.provider stays unchanged. The
held CTO maps to opus too. Only the two exact provider/model limits are set:
opus four, sonnet four. There is no total limit or provider-wide limit.
The adjacent JSON Patch names the exact entry changes. Jason supplies his
organization-file path to this command, after checking the output destination:
```sh
ORGANIZATION=/path/to/organization.json
scripts/mosaic limits migrate --file "$ORGANIZATION" --models /mnt/storage/src/mosaic-stack/agents/rocko/work/queue-122/r1/migration-map.json --roles-dir roles > /path/to/converted-organization.json
```
Run the command from the newly reviewed copy so the command and role definitions
match. It reads fixtures in builder/reviewer gates; no agent runs it on the live
file. It does not contact a bus, write its input or offer --write. Jason reviews
and installs the complete output himself; the patch is explanatory, not an
automatically applied migration.
Jason's edit and the re-pin happen in one window Mos runs, with the PM run idle.
Old code cannot consume the new shape, and new code refuses launch.max. Keep the
converted document and reviewed copy ready before the window; do not migrate
early or re-pin early. Provider remains a table value for a later proxy change.
+42
View File
@@ -0,0 +1,42 @@
# Row122 mutants
| Check / fault | Result | Test |
| --- | --- | --- |
| 01-whole-document-validation | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 02-atomic-publication | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 03-previous-version | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 04-authenticated-actor | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 05-old-new-receipt | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 06-stale-revision | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 07-uncertain-recovery | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 08-total-limit | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 09-provider-limit | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 10-model-limit | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 11-at-limit | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 12-unset-unlimited | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 13-nonnegative-counts | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 14-no-policy-ceiling | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 15-exact-provider-count | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 16-provider-routing | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 17-reread-every-launch | KILLED (1) | packages/cli/tests/launch-limits.test.mjs |
| 18-refusal-hit-logged | KILLED (1) | packages/cli/tests/launch-limits.test.mjs |
| 19-migration-preserve-counts | KILLED (1) | packages/cli/tests/limits.test.mjs |
| 20-migration-ambiguity | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 21-print-only-migration | KILLED (1) | packages/cli/tests/limits.test.mjs |
| 22-human-only-settings | KILLED (1) | packages/bus/tests/limits.test.mjs |
| 23-settings-origin | KILLED (1) | packages/webui/tests/settings-write.test.mjs |
| 24-settings-fetch-metadata | KILLED (1) | packages/webui/tests/settings-write.test.mjs |
| 25-settings-send-mode | KILLED (1) | packages/webui/tests/settings-write.test.mjs |
| 26-settings-strict-keys | KILLED (1) | packages/webui/tests/settings-write.test.mjs |
| 27-form-instances | KILLED (1) | packages/webui/tests/settings-form.test.mjs |
| 28-form-limit-payload | KILLED (1) | packages/webui/tests/settings-form.test.mjs |
| 29-migration-idempotent | KILLED (1) | packages/cli/tests/limits.test.mjs |
| 30-zero-stops-new | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 31-instance-model-edit | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 32-model-table-edit | KILLED (1) | packages/business/tests/launch-settings.test.mjs |
| 33-runtime-writer-wiring | KILLED (1) | packages/bus/tests/limits.test.mjs |
All mutations restored their original bytes in finally blocks. Each command was
`node --test TEST`, with DOCKER_HOST=unix:///nonexistent.sock in a new process
group. Whole-group SIGKILL on timeout; timeouts are failures, never kills.
No timeout, syntax error or missing module contributed a kill.
@@ -0,0 +1,5 @@
{
"base": "f529cf485c8a0ac214076052cb8e16f14864c7a0",
"canonicalHead": "46117feb87d38aa2c1393244113acf5864416a24",
"drift": []
}
+1
View File
@@ -0,0 +1 @@
f529cf485c8a0ac214076052cb8e16f14864c7a0
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,43 @@
59de512efd425a045b9ae00f0192b49d3cf832665c5710743ca331cb4c885ba8 docs/TOOLS.md
4f7a196c4fcf6ba2f07bb35294217cd456cd63cc53b85f707714199ce71f7e4b packages/bus/src/broker.mjs
c486eeed430afc1fd1de70c5b3e4299bbab392c04a4bc29167def6d1b39c9825 packages/bus/src/runtime.mjs
c64427cfbffd5ec32532d5d18009e5080185c14d9c831cd67b5acbd64e4ccee6 packages/bus/tests/limits.test.mjs
37c129d82bc3194a3a21be5510f67b9943e77ed87c6736667ea91f9ca6b836db packages/business/README.md
6704bb0d86fd68fcae9c0483ad99577ffbbbf61a9c2db8366697f8aebd152f40 packages/business/examples/mosaic-stack.example.json
4f35eded02507e3cbf5c392b8b96d5abedd51738e3b235bf623ed26cff9aba61 packages/business/src/business.mjs
4c09f9eeba15f5589ad7a3cfcb7547c713d313fb08541e11ce532695cbb24f55 packages/business/src/index.mjs
020c9c7c8097c35a987be3be3ee4ffe4021d861a37cb89a8971c00b6fa2a32a0 packages/business/src/launch-settings.mjs
42afbe81d2017b056098773b479c934d351af2a65d6fe0b67b7713abc32cdbc4 packages/business/src/launch.mjs
40b296a2a9aec340c0c154ef467c24aecba46d92c5e17edb4b08144609e4c6b7 packages/business/src/migrate-launch.mjs
9ae19e9e637e2f668c10455c93085b834817bf9b793e17cee2f1657c8a71197d packages/business/src/organization.mjs
b551725cea3cd2699f186b988d1cc627df00d9906f2129ae4e5d667d7cff66d4 packages/business/src/vocabulary.mjs
92b07694e40878e92f71e2cb3a539024dca54fbcf5bd71b246571d846959ad87 packages/business/tests/business.test.mjs
251d38ac3ba90330c4ff8a8dd3032c8a8fb73037d6819c3ac60a47daefce8fd6 packages/business/tests/chief-of-staff.test.mjs
5bfa5a9348f216d67451eb5dbe360d8004055937e6f5e8a6988076faa3a7ac27 packages/business/tests/helpers.mjs
c6274d94b559760b431e206a235d2bbf3f45801cff15ddd88666fc10654b673c packages/business/tests/launch-settings.test.mjs
e49ddf4bbea1266c272effd7bc15988c5f2f6b55bf8bf0762082ab622b46fabb packages/business/tests/resolve.test.mjs
0488d15c54e05bfa6129ba971e5beefbf928b62159250d30a62c18a47aac9190 packages/cli/README.md
20f8c4152e4d49d02695c18fe356556789dcb2ee221bbe7cca427d87e4759f28 packages/cli/src/cli.mjs
de3b06388f02839b55d27e0821907994bd5ab2e07447a1bf1ba72c0c8127b194 packages/cli/src/config.mjs
d84d7fa771e84b84e5ba510e24f3d952272d59bbfaf2b1b8128e1b26fc078e47 packages/cli/src/launcher.mjs
b50f7c4924dabacfc03b5fca4eb9cd34e75cdce43ffc5b9405b25082c20dc505 packages/cli/src/limits.mjs
a4207650a0ad5d41226c893870a38f20315ff8e8ba53165c5e97d1b9b4a159d6 packages/cli/tests/launch-limits.test.mjs
8096a4efa5c4728aba25483e2d1454c5c71dd3c188d76e5e7c91bf1e8a2f5591 packages/cli/tests/launcher.test.mjs
427d0f6fc8aebfe50e15d98691f9a99061311d087e665a5c38a60cc39222c7ae packages/cli/tests/limits.test.mjs
00b905170134f7154d1851dabcd93318dbc5446ff29f80b00c459c61df36d525 packages/harness/src/bundle.mjs
20f91e3423661d442ba7c01280b0776a75cb10050d86ffdee9208d497957e56d packages/harness/tests/pi-session.test.mjs
4085e77fe81093b2938cd1f97ff40cd30dbc6684b5f37ba617cc404763263e2c packages/harness/tests/runner.test.mjs
86ed3187d74cc3eecacd641aefaa96cff4a04b39a37069059dbd274914699ac1 packages/seat/src/session.mjs
bcbc021d1984f93b9ac682bf53e7dfa6a1e7aa4029bb115bc800b862af40e0e0 packages/seat/tests/session.test.mjs
a035076c6596e6de0d634d3fb5a1b2859800ad2346811dc747ff86c0c8b612b4 packages/webui/src/bus.mjs
7855d1e29e2561eae21ee6c429f3a8fc085f429cc7a25909185db909e8dc9ee8 packages/webui/src/cli.mjs
a75bdd407e379be9ea87741db9413dfc4eb85d8d774f994762f475fcde635e3c packages/webui/src/public/bus.js
f6fc93ae58d5af73848567164b6d5c7b6e8d506e9c32b90851247adb9373282f packages/webui/src/reads.mjs
c69b76b7489c261b992dc1d6c680e51fd960a7c9f24e07148f7b59eb40e91d75 packages/webui/src/serve.mjs
7b9b03f183c603227a53df5a8824348f063a46161ceced32796f459160f9eae1 packages/webui/tests/reads-fixture.mjs
d8cd0b9af738245f0ccf44e02106533a41a43d8fc6a8c189458afd83825ca0c1 packages/webui/tests/reads.test.mjs
955b0c9f8bbc2c7c71dad45aee0890eed54e34198bd7970961bc180b5bff68b2 packages/webui/tests/settings-form.test.mjs
b8433e87f04753be7b5ce3cf6f93173a30eada336cf360b8420b8ff75696f2e6 packages/webui/tests/settings-write.test.mjs
c55237b952cdd860828e12c6b746cdfeddda6c85a48b986587abaf01a7534963 packages/webui/tests/shell.test.mjs
7cef4e9c8caa93dc68a7b2057c540da3eafe07c6258c884c3f52e10a46382cb1 packages/webui/tests/views.test.mjs
db18760a6e93c0db45d272da278717af9844e06c2b859607971661b0aee82121 scripts/mosaic
@@ -0,0 +1,3 @@
pkg-business 0
pkg-bus 0
pkg-cli 0
@@ -0,0 +1,13 @@
pkg-business 0
pkg-bus 0
pkg-seat 0
pkg-harness 0
pkg-cli 0
pkg-webui 0
pkg-control-board 0
pkg-conversation 0
pkg-ledger 0
pkg-tasks 0
script-queue 0
script-discord 0
script-task 0
@@ -0,0 +1,7 @@
pkg-business 0
pkg-bus 0
pkg-seat 0
pkg-harness 1
pkg-harness 0
pkg-cli 0
pkg-webui 1
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
"""Decision136 builder gate. No live files or services; processes isolated by group."""
import os,pathlib,signal,subprocess,sys,json
root=pathlib.Path(sys.argv[1] if len(sys.argv)>1 else '/mnt/storage/src/mosaic-stack-rocko-122');out=pathlib.Path(__file__).resolve().parent/'gate';out.mkdir(exist_ok=True)
env={**os.environ,'DOCKER_HOST':'unix:///nonexistent.sock'}
steps=[('pkg-'+p,['node','--test','--test-concurrency=1','--test-reporter=spec',*map(str,sorted(pathlib.Path('packages',p,'tests').glob('*.test.mjs')))]) for p in []]
# Discover paths from the candidate, not the directory from which the runner is invoked.
steps=[]
for p in ['business','bus','seat','harness','cli','webui','control-board','conversation','ledger','tasks']:
files=[str(f.relative_to(root)) for f in sorted((root/'packages'/p/'tests').glob('*.test.mjs'))]
steps.append(('pkg-'+p,['node','--test','--test-concurrency=1','--test-reporter=spec',*files]))
# The changed script dispatcher is read by queue and discord; task resolves changed business code.
for p in ['queue','discord','task']:
steps.append(('script-'+p,['bash','scripts/test-'+p+'.sh']))
results=[]
if os.environ.get('GATE_ONLY'):
names=set(os.environ['GATE_ONLY'].split(','));steps=[s for s in steps if s[0] in names];results=[r for r in json.loads((out/'results.json').read_text()) if r['suite'] not in names]
if len(sys.argv)>2:
start=next(i for i,s in enumerate(steps) if s[0]==sys.argv[2]); prior=json.loads((out/'results.json').read_text()); names={s[0] for s in steps[start:]};results=[r for r in prior if r['suite'] not in names];steps=steps[start:]
for name,cmd in steps:
with (out/(name+'.log')).open('w') as log:
step_env=dict(env)
if name=='script-task':step_env.pop('DOCKER_HOST',None)
child=subprocess.Popen(cmd,cwd=root,env=step_env,stdout=log,stderr=subprocess.STDOUT,start_new_session=True)
try:rc=child.wait(timeout=1200)
except subprocess.TimeoutExpired:os.killpg(child.pid,signal.SIGKILL);child.wait();rc='timeout'
results.append(dict(suite=name,exit=rc,command=cmd));(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,166 @@
✔ agents: a held role with nothing on the bus is idle; a role session gets the claim rows unchanged (14.844026ms)
✔ agents: working counts what the holder received and has not read; routed alone is not working (12.84997ms)
✔ agents: input needed is the role's open decisions routed to the human, gated first, then oldest (24.058901ms)
✔ agents: working wins over waiting, and Input needed still shows (9.610497ms)
✔ agents: a new holder doesn't inherit what the last one left unread; decisions stay with the role (12.163566ms)
✔ agents: state reads only its own business, even for a run name both use (11.497937ms)
✔ agents: a message the holder released unread is stranded; the next holder gets nothing and cannot read it (13.045578ms)
✔ agents: a message the holder read is not stranded; one the current holder has not read is not listed (13.691207ms)
✔ agents: stranded is oldest first and capped at 50 for a role (60.514285ms)
✔ agents: stranded is per role and per business (22.578807ms)
✔ agents: a message routed to a held role and not yet received is not stranded (8.480099ms)
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (14.332615ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (16.273286ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (16.436354ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (10.662472ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (10.682626ms)
✔ a recommendation that is not exactly one option key refuses invalid-recommendation after the options (15.903014ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (9.710863ms)
✔ task action subjects and linked decision trail are complete and ordered (17.117199ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (6.66024ms)
✔ business isolation includes inherited object names and cross-business message references (12.1309ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (16.039823ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (8.367375ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (13.120536ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (8.469146ms)
✔ both arbiters require human resolution when their cross-role route is themselves (14.163195ms)
✔ the shipped coder role raises task.reassign as gated to the human; task.scope.change stays cross-role (14.202759ms)
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (1.277516ms)
✔ credentials gives human and reader sessions service, account, role, date and state, and nothing else (11.733145ms)
✔ credentials refuses a role session and any argument (11.043972ms)
✔ credentials reads the status on each request, and a broker without one lists none (12.679265ms)
✔ the broker process answers credentials without a token, path or variable in its output, log or store (60.659373ms)
✔ only validated broker references load; returned data and exceptions cannot expose a known token (2.434158ms)
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (1.398852ms)
✔ expiry refuses use and env references never become client data (0.427332ms)
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (0.982795ms)
✔ opaque tokens shorter than 16 characters refuse before use (0.225205ms)
✔ a human resolve sends the raising role one message from the human, and its request is the answer (24.262806ms)
✔ a cross-role decision resolved by its arbiter comes from that role and run, with no request (13.779696ms)
✔ a human withdraw sends one message with the note (13.946399ms)
✔ a human seen before the close leaves the request on the close's answer, not the seen (16.592904ms)
✔ the raising run's withdraw, a supersede, a within-role raise and seen send nothing (16.832577ms)
✔ refused closes send nothing: invalid-choice, decision-closed, not-resolver (17.774439ms)
✔ a raising role nobody holds at the close gets the message on the next claim (15.435615ms)
✔ a human DECISION send keeps its own instruction as the request, not a close's answer (14.373193ms)
✔ roles lists open decisions routed to a role, and never the human inbox (18.826278ms)
✔ recent lists closed decisions of every route, newest close first, with how each closed (18.860754ms)
✔ recent gives the closing event as closed when the decision was seen first (18.099987ms)
✔ recent stops at RECENT rows (61.151889ms)
✔ every row is what trail already gives the same session for that id (25.72803ms)
✔ a role session can't list decisions, so it can't read another role's open decisions (26.245239ms)
✔ decisions is a read: one business, strict arguments, nothing written (24.325387ms)
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (11.888158ms)
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (12.142498ms)
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (11.299436ms)
✔ endLaunch leaves a claim another run took alone (11.192429ms)
✔ refuse records action.refused against the caller with the code only (8.878521ms)
✔ launches off refuses role.launch with launch-revoked until launches on (12.2991ms)
✔ broker process: launch ops authorize role.launch, record refusals and end runs (74.551448ms)
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (1.422526ms)
✔ process reader gets own kernel identity without exposing environment values (0.532394ms)
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.673358ms)
✔ real pid 1 remains inspectable when its environment is protected (0.226372ms)
✔ limits binds actor from human proof and rejects role, reader, tester and forged actor (14.707559ms)
✔ runtime wires verified human settings to the actual shared file writer and actor receipt (30.922283ms)
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (16.797386ms)
✔ missing and foreign-business citations refuse and roll back message and grant (11.38752ms)
✔ cross-role sends still need a matching resolved decision and consume it once (15.162452ms)
✔ messages pages all human messages newest first without writes; read state is history (221.892602ms)
✔ messages refuses role callers, invalid paging and cursors outside this human feed (17.100906ms)
✔ views messages passes only its paging arguments through (0.259275ms)
✔ messages isolates businesses even with a valid foreign cursor (11.524709ms)
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (63.405794ms)
✔ startup token refusal returns safe code without value or partial listening broker (45.269985ms)
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (60.060361ms)
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (86.501212ms)
✔ trusted host registers later launches; socket clients never have a registration verb (60.928301ms)
✔ runtime excludes declared project roots even when host supplies no repoRoots (45.211966ms)
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (62.162379ms)
✔ a reply is listed for human and reader sessions, and its delivery rows are unchanged (23.889961ms)
✔ the reply still reaches the human message.receive (mosaic talk) after the Console showed it (12.39846ms)
✔ a message with no reply gives an empty list (9.48174ms)
✔ replies come oldest first and stop at REPLIES (49.716775ms)
✔ an id that names no message in the business refuses message-not-found, and the refusal is recorded (20.072931ms)
✔ strict arguments; a role session gets human-required (22.342641ms)
✔ views passes replies through as one call with the id (0.260172ms)
✔ requirements returns the committed list to a holding role session, and no one else (16.641329ms)
✔ the PM's prd.approve with no task, when no id fits, reaches the human's Inbox (9.395777ms)
✔ the PM's non-blocking role.revoke with no task reaches the human's Inbox (9.59052ms)
✔ a push is recorded as the action.allowed for git.push.working on the task, and only the host records one (19.512113ms)
✔ review.request names the latest pushed commit and sends the reviewer a REVIEW-REQUEST (14.089612ms)
✔ review.request says which run holds the reviewer role, or null, and a null request waits for a claim (17.15279ms)
✔ review.request refuses a commit that isn't the latest push, a non-reviewer and a role without the action (23.373996ms)
✔ reviewFetch answers only the addressed reviewer, for the request it cites (14.766611ms)
✔ review.verdict answers one request once, to the role that asked (19.524567ms)
✔ task.close on a pushed task needs an approve verdict for the latest push (24.137758ms)
✔ a refusal the host decided is filed on the task it names, and only a task ref counts (16.089347ms)
✔ v3b prototype refusals, views and append-only mutations (49.431895ms)
✔ gated approval authorizes once, survives store reopen, and fresh approval works (19.654508ms)
✔ another run cannot consume an approval; a failed check leaves it usable (13.371078ms)
✔ two scheduled callers have exactly one grant and one consumed refusal (10.341648ms)
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (20.298967ms)
✔ class drift gated to cross-role refuses before consumption (14.035778ms)
✔ class drift cross-role to gated refuses before consumption (14.607792ms)
✔ class drift gated to within-role refuses before consumption (13.626736ms)
✔ class drift cross-role to within-role refuses before consumption (17.835935ms)
✔ class drift within-role to gated refuses before consumption (11.807707ms)
✔ class drift within-role to cross-role refuses before consumption (12.243654ms)
✔ message.send consumes approval and prevents a later send or authorize (14.383965ms)
✔ role.revoke consumes approval and prevents a later revoke or authorize (17.045277ms)
✔ a business that never switched launches reads allowed with no time and no event (11.499786ms)
✔ launches revoked and then restored read allowed, with the restore time and event id (8.844644ms)
✔ a task closed on the tracker leaves the open list, and total counts the open ones (9.451341ms)
✔ a tombstone is not open (7.491171ms)
✔ the open list stops at the cap and says so; total still counts every open task (10.774382ms)
✔ a role and a reader session read state; arguments, and an unbound cap, refuse (8.625774ms)
✔ a human session reads the same state, with the same cap (10.692408ms)
✔ reading state writes nothing: every table keeps its row count (8.892325ms)
✔ creates private WAL store and excludes a second writer until explicit close (9.611322ms)
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (9.573132ms)
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (8.974715ms)
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (39.250321ms)
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (7.215517ms)
✔ async transactions refuse before invoking their function (5.430372ms)
✔ the session default fits under the reply cap and pages to the end with next (122.72866ms)
✔ the short form: no description, assignees by role, every listed key (73.228304ms)
✔ each filter, alone and together, and total counts every match (158.759482ms)
✔ task_ref reads one task in full, as the whole board has it (91.831499ms)
✔ no arguments is the read the Console and mosaic make, unchanged (72.869861ms)
✔ an unknown argument or a bad value refuses invalid-request (323.431485ms)
✔ tasks rows carry priority and requirement for every session that reads tasks (26.185887ms)
✔ the read model rebuilds from the ledger: a reopened bus and each trail give the same values (28.12096ms)
✔ an empty business and one with snapshots but no task.created read cleanly (6.885219ms)
✔ recordTask keeps sync reads and a role write apart (15.927479ms)
✔ read_at must be one canonical UTC format, so the projection compares strings safely (6.046892ms)
✔ a bad entry refuses the whole record (7.099587ms)
✔ taskView reads the projection for one business (7.452228ms)
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (14.539223ms)
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (275.563631ms)
✔ without an adapter the server refuses every task verb (16.321608ms)
✔ the runtime refuses an invalid adapter and closes a valid one (16.333511ms)
✔ the process loads the S3 adapter from plain-data trackers (112.065291ms)
✔ the tester binds only when the business sets bus.tester to exactly true (47.309984ms)
✔ a business may not name a role tester, so the tester address is never a role (14.194129ms)
✔ every verb outside the tester list refuses tester-refused and is recorded against the tester (30.940516ms)
✔ a tester message is from tester, starts [mos test], and writes no human.input or authority (13.036098ms)
✔ a role answers a tester only in reply to a tester message, and the answer never reaches the human (16.731485ms)
✔ the tester reads the human views a reader reads (18.626743ms)
✔ tester proof binds its own CLI, start time and nonce, refuses a managed run and allows an agent (1.222216ms)
✔ neither CLI's nonce proves the other: each proof is bound to its own CLI and nonce variable (0.343674ms)
✔ testerNonce reads only the tester's variable and gives null on any read error (0.20371ms)
✔ an envelope carries exactly one of cap, human and tester; no tester authenticator refuses (16.118503ms)
✔ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (649.730112ms)
✔ socket capability stamps launch identity; shared views use wire, no SQL client (19.448606ms)
✔ two wire claims serialize; a lost reply never automatically retries (13.892211ms)
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (7.902246ms)
✔ client preserves UTF-8 when a response divides a multibyte character (11.78367ms)
✔ committed mutation followed by dropped reply reports unknown and is never retried (9.56165ms)
ℹ tests 158
ℹ suites 0
ℹ pass 158
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5885.311646
@@ -0,0 +1,83 @@
✔ config directory and file path follow MOSAIC_CONFIG (1.18386ms)
✔ the fixture business validates and comes back frozen (3.580921ms)
✔ two instances may share a definition (1.13356ms)
✔ top-level refusals (3.055382ms)
✔ arbiters and projects (4.175821ms)
✔ role instances (2.100841ms)
✔ Vikunja bots (5.371142ms)
✔ a role without Vikunja takes no tracker block (1.948392ms)
✔ credential references match the definition's services (2.234079ms)
✔ launch (8.242374ms)
✔ loadBusiness: file checks (1.740668ms)
✔ loadBusiness: not a regular file (40.650523ms)
✔ loading writes nothing (1.145129ms)
✔ names that are Object.prototype properties don't count as declared (2.617169ms)
✔ the shipped example refuses as written and validates once filled in (0.598597ms)
✔ the role file loads and resolves through loadBusiness (4.456847ms)
✔ the shipped file is exactly decision 104's list (0.564224ms)
✔ the resolved record matches decision 104, so a widened file fails (1.372699ms)
✔ message.send is within role and every other action is gated (1.102117ms)
✔ a business grant of a gated action through limits does not widen the role (1.098151ms)
✔ a launch block whose by is the chief-of-staff instance is refused (1.362043ms)
✔ the instance takes no credential references and no tracker (1.899047ms)
✔ usage errors exit 4 (278.455448ms)
✔ validate: a good business exits 0 and prints instance digests (66.302021ms)
✔ validate: project files (332.045786ms)
✔ validate: missing files and a broken system config (253.615006ms)
✔ validate: credential reference problems exit 2 and name each one (68.879982ms)
✔ validate: a token file inside the repository is refused (67.533211ms)
✔ validate: role definitions come from MOSAIC_ROLES_DIR (205.856296ms)
✔ resolve: prints one instance's record (204.121726ms)
✔ resolve: refusals (393.464875ms)
✔ parse: exactly one of file or env, plus the service's date (2.004385ms)
✔ check: a good file has no problems (0.652115ms)
✔ check never opens the file: a write-only token passes (0.319827ms)
✔ check: file problems (0.632456ms)
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (0.652236ms)
✔ check: dates and environment references (0.317422ms)
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.573375ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.653926ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (17.49534ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (14.528225ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.044508ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (16.250792ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.874518ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.994965ms)
✔ path and load (1.756178ms)
✔ refusals (0.861804ms)
✔ systemVars flattens the validated config (1.525582ms)
✔ precedence: system, business, project, project role, agent (4.080718ms)
✔ limits narrow the definition and never widen it (1.765003ms)
✔ role.launch stays within-role only for the instance the launch block names (3.530498ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (1.455845ms)
✔ limits.authority narrows cross-role actions too (0.782821ms)
✔ classify (0.907971ms)
✔ the record carries what the broker and launcher need (0.80907ms)
✔ digest: key order doesn't matter, any value change does (5.292689ms)
✔ refusals (2.106715ms)
✔ the four shipped version 2 roles load (2.500675ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (0.75362ms)
✔ shipped authority follows the note's table (0.42448ms)
✔ version 1 files keep loading with no authority (0.850995ms)
✔ the conductor policy isn't a role (0.166299ms)
✔ a missing role file is exit 4, a symbolic link too (0.319969ms)
✔ version 2 refusals (1.049353ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (1.575016ms)
✔ credentials: Gitea scopes (0.638998ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (0.848161ms)
✔ credentials: services (0.436933ms)
✔ contract: a non-empty regular Markdown file beside the role file (0.507218ms)
✔ every key names known layers and a merge rule (0.671028ms)
✔ unknown keys and wrong layers refuse (0.554604ms)
✔ types (1.454297ms)
✔ merge: defaults, then the most specific layer wins (0.221482ms)
✔ merge: limits only narrow, and provenance lists each source (0.260633ms)
✔ merge doesn't change its inputs (0.104325ms)
ℹ tests 75
ℹ suites 0
ℹ pass 75
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2500.242245
@@ -0,0 +1,204 @@
✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (1278.704706ms)
✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (67.101096ms)
✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (154.69603ms)
✔ NO_COLOR: no escape codes, except reverse video on the selected row (232.701302ms)
✔ 1-9 shows the decide flow's own lines and question; only y resolves (49.702496ms)
✔ the app shows the same decide text as mosaic decide (21.498969ms)
✔ any key but y or Y at the question answers No; nothing is sent (189.635405ms)
✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (67.38363ms)
✔ under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10) (252.87903ms)
✔ a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12) (31.111355ms)
✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (21.272017ms)
✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (406.393948ms)
✔ quitting at the question answers No and sends nothing, armed or not (31.559779ms)
✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (29.539585ms)
✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (45.860524ms)
✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (288.6538ms)
✔ at every size the decide box shows each line y depends on, or says it is hidden (8804.337388ms)
✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (74.762976ms)
✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (30.73264ms)
✔ help or a detail box cut to fit keeps its last line, Esc included (Dewey) (15.599306ms)
✔ keys: views, moving, trail of a selection, the command line, help and quit (79.945703ms)
✔ one option: the status line says Press 1, not 1-1 (15.981151ms)
✔ no business: the app says so and :business picks one (9.246726ms)
✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (2.12037ms)
✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (179.815355ms)
✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.469133ms)
✔ decide with --note still asks; a No sends nothing (9.983645ms)
✔ the app's business comes from a live bus host only (1.236473ms)
✔ 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.324949ms)
✔ draw is pure: same state, same frame (0.812099ms)
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (15.445037ms)
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (12.421187ms)
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (10.171392ms)
✔ decide prints a declining choice as declining (9.32729ms)
✔ an unknown outcome is reported once and never resent (8.527882ms)
✔ a decision closed before the answer arrives exits 2 and points at its trail (8.56156ms)
✔ a prefix that matches two open decisions exits 2 and resolves neither (8.79217ms)
✔ without --business a command uses the live host's business, and a stale host.json is not a host (7.63574ms)
✔ every human command refuses inside an agent run before it touches the bus (7.475851ms)
✔ usage errors exit 4; no business and no host is a usage error (8.263908ms)
✔ agents and tasks print through the broker (7.649261ms)
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.221258ms)
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (33.647505ms)
✔ trackers come from the tracker.* variables of the one project that names a tracker project (57.423514ms)
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (27.827898ms)
✔ two projects that each name a tracker project refuse, since the boot shape holds one (33.083445ms)
✔ a business without tracker.baseUrl gets no trackers entry (29.587825ms)
✔ an unknown business and a broken system config refuse with exit 3 (53.080733ms)
✔ follow prints an unsolicited message after plain talk returned (23.414197ms)
✔ talk --follow continues after reply and prints a later unsolicited message (23.489038ms)
✔ inbox lists all unread human messages under decisions, does not deliver or read them (204.112686ms)
✔ follow refuses agent context and bad arguments (8.332772ms)
✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (9.002956ms)
✔ plain talk leaves the process SIGINT handlers unchanged (9.574746ms)
✔ empty views say so (0.687517ms)
✔ the trail keeps the broker's order and names a decision's task without its rows (0.807911ms)
✔ tasks print the tracker fields the snapshot carries (0.129675ms)
✔ the review loop: the coder pushes and asks, the reviewer fetches and approves, and the task's trail holds each step (309.910835ms)
✔ refusals: no action, non-fast-forward, an unknown commit or request, and a push for another task moves only that task's ref (318.91968ms)
✔ the size cap refuses a push before any byte (148.721924ms)
✔ a fetch over the cap refuses before any byte reaches the reviewer (310.749936ms)
✔ the socket refuses a request with no capability, an unknown one, and a bad request line (148.091236ms)
✔ a rigged clone runs nothing: hooks, fsmonitor, a filter, a gitlink and a symlinked .git/config leave no marker (252.37343ms)
✔ an identity with no role, or of another business, refuses not-a-role and moves no ref (191.79541ms)
✔ a review of a project the business no longer has refuses unknown-project (186.143444ms)
✔ past maxActive connections the socket refuses git-busy, and a hung-up connection frees its slot (129.96021ms)
✔ close() answers host-closing to a connection still sending its request line (130.290264ms)
✔ a bundle that trickles in refuses body-timeout at its deadline and frees its slot (541.923639ms)
✔ a reviewer gone before its bundle was sent is filed fetch-aborted, not as the host's fault (417.761507ms)
✔ a reviewer that hangs up the moment it has the whole bundle was served, not aborted (478.166313ms)
✔ send: a close after every byte left is served; one that fails a queued write is fetch-aborted (42.009833ms)
✔ a bundle with no bytes for bodyIdle refuses body-timeout long before its deadline (447.646311ms)
✔ send: a close once the loop is past its last chunk is served, and a failed write there is fetch-aborted (23.899623ms)
✔ letGo waits for an answer to leave for at most its cap, then destroys the socket (302.537137ms)
✔ a record that fails and can't be undone refuses rollback-failed, which the host logs (327.539306ms)
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (824.682544ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (150.209323ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (84.168343ms)
✔ two bus starts racing for one data root leave one host running; the other refuses that a host already runs (457.956777ms)
✔ a host takes over a host.lock its dead host left, and puts back a live claim that replaced it meanwhile (1.817915ms)
✔ 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.75735ms)
✔ a live host's lock stays, however old the file is (0.474798ms)
✔ a host.lock left by a host killed with SIGKILL doesn't stop the next bus start (762.452242ms)
✔ while a host boots, a reader sees no host: the human commands still ask for --business, stop and status find none (211.651983ms)
✔ a host started before host.lock existed, holding only host.json, still refuses a second start, which leaves no lock (28.888665ms)
✔ a broker fork that throws leaves no host.lock and no host.json (35.658326ms)
✔ a broker send that throws leaves no host.lock and no host.json (30.005067ms)
✔ a broker that misses its boot wait leaves no host.lock and no host.json (31.664316ms)
✔ a notifier fork that throws stops the broker and leaves no host.lock and no host.json (92.356977ms)
✔ a notifier send that throws stops the broker and leaves no host.lock and no host.json (85.179268ms)
✔ 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 (89.363107ms)
✔ 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 (96.340225ms)
✔ a host.json that can't be written stops both children and leaves no host.lock (202.056182ms)
✔ an error event on the broker before it replies fails the start at once, and leaves no host.lock and no live child (33.233068ms)
✔ an error event on the notifier before it replies fails the start at once, and leaves no host.lock and no live child (81.738181ms)
✔ an error event on the broker while the notifier starts fails the start once the notifier replies (191.888155ms)
✔ 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 (188.378305ms)
✔ 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 (188.869775ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1088.017083ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (162.145253ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (82.498429ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (132.441485ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (129.332943ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (75.470125ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (131.472463ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (83.33189ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (140.237861ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (21.443295ms)
✔ bus stop refuses to signal a live pid that is not a bus host (202.705832ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (255.646099ms)
✔ bus start refuses with exit 3 without a notifier config (106.595628ms)
✔ bus start runs until bus stop; status reports it while it runs (707.287367ms)
✔ bus-service.sh renders the unit and installs it into a given directory (58.735833ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (73.849685ms)
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (14.699619ms)
✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (9.071232ms)
✔ each launch rereads limits and model selections; lowered limits keep sessions and log exact hit (22.048257ms)
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1215.210163ms)
✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (567.015844ms)
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (3542.382384ms)
✔ a project clone that can't be made refuses checkout-failed, from --pm and from a session, with no run and no clone left (121.871852ms)
✔ a runner that stops at once ends its launch with the runner's reason (190.750037ms)
✔ a Claude Code launch with no session credential refuses before it starts (94.678967ms)
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (183.350054ms)
✔ a runner killed from the host ends its launch as killed, not failed (243.708479ms)
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (103.265966ms)
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1058.178885ms)
✔ 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 (631.04212ms)
✔ 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 (3575.03614ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (89.901345ms)
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (93.372049ms)
✔ a launch client that never closes its side doesn't hold the host's close (96.932271ms)
✔ a runner that ignores SIGTERM is killed when the host closes (1225.325197ms)
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (4.777212ms)
✔ migration needs no bus/config, prints whole converted document, is idempotent and never writes (8.788908ms)
✔ limits view/set use the human bus transport and pass only the explicit patch (1.130774ms)
✔ zoned uses the IANA zone across DST (12.223992ms)
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (15.160599ms)
✔ two blocking decisions get two DMs with different nonces (9.77422ms)
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.187995ms)
✔ a failed DM is journaled, backs off, and is retried until it lands (9.906592ms)
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (11.869016ms)
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (13.092666ms)
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (8.828931ms)
✔ 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 (70.484892ms)
✔ a restart after the second refusal does not send before that refusal's 30 min are up (36.140142ms)
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (10.136365ms)
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (8.004457ms)
✔ an inbox read failure is logged and the next poll retries (0.660019ms)
✔ no Discord id reaches the journal or the log (8.501141ms)
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (1.977196ms)
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.282988ms)
✔ the journal: a whole file that is one torn line truncates to empty (1.378782ms)
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.532459ms)
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.549922ms)
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.781663ms)
✔ the journal: a symlinked directory refuses and says it is a link (1.715995ms)
✔ 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.466661ms)
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.367042ms)
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.427882ms)
✔ digest content stays within Discord's 2000 characters (0.270176ms)
✔ runLoop never overlaps ticks and stops after the one in flight (111.851273ms)
✔ a human command refused with human-required prints the other-tree line and exits 3 (2.205783ms)
✔ a --tester command refused with tester-required prints the other-tree line and exits 3 (0.755949ms)
✔ other exit-3 refusals print no other-tree line (1.027143ms)
✔ the line names no path (0.080224ms)
✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (422.653413ms)
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (406.648568ms)
✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (400.787255ms)
✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (275.50621ms)
✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (3283.281506ms)
✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (608.139532ms)
✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (192.28216ms)
✔ install never shows a template name on systemd 261; template drop-ins still refuse (218.849037ms)
✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (185.836867ms)
✔ inherited mosaic-.service.d override refuses without changing the unit (234.547851ms)
✔ inherited service.d override refuses without changing the unit (235.087609ms)
✔ inherited drop-in unreadable directory or file refuses before writing (187.22481ms)
✔ talk --tester sends a [mos test] message as tester from inside an agent and prints the role's reply (28.939242ms)
✔ talk --tester refuses text without the [mos test] prefix before any call (10.358056ms)
✔ the read verbs take --tester inside an agent, and the trail shows the tester as the sender (16.265535ms)
✔ decide, stop and launches take no --tester (10.993645ms)
✔ the tester transport runs tester-cli.mjs, and the tester's refusals exit 3 (0.607425ms)
✔ with no stand-in transport, --tester runs tester-cli.mjs on the data root's socket (1.807292ms)
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 (304.117008ms)
✔ the transport writes {business, verb, args} to the child and reads its JSON (42.021624ms)
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2188.859738ms)
✔ busExit and refuseInsideAgent (0.510023ms)
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (42.992061ms)
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1016.166315ms)
✔ a late reply the Console has listed still reaches the next talk (2022.348286ms)
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (16.262453ms)
✔ launches off and on go to the broker and change the business's launch state (12.012683ms)
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (12.491876ms)
ℹ tests 194
ℹ suites 0
ℹ pass 194
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 52132.36275
@@ -0,0 +1,132 @@
✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (3.75205ms)
✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.237076ms)
✔ completed smoke replies and ordinary questions are idle, not human blockers (0.740896ms)
✔ only an explicit first-line input request makes a finished reply waiting (0.181397ms)
✔ tool activity, user text, errors and unfinished turns override attention text (0.128617ms)
✔ STOP access failure is unknown, not absence, under a non-root identity (27.65209ms)
✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (2.878614ms)
✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (1.440799ms)
✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (4.933163ms)
✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.534542ms)
✔ server rescans connector discovery and refuses HTTP reply without transport (31.558183ms)
✔ connector session links and linked directories are not read (1.12112ms)
✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (2.528749ms)
✔ CLI print uses the relaunch notice instead of old current preview (59.628491ms)
✔ connector owner and fixed task never inherit a native relaunch notice (1.837603ms)
✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.488687ms)
✔ loadConfig: missing file throws ConfigError (1.348379ms)
✔ loadConfig: invalid JSON throws ConfigError (0.283309ms)
✔ loadConfig: missing dataRoot throws ConfigError (0.226376ms)
✔ loadConfig: relative dataRoot throws ConfigError (0.217024ms)
✔ loadConfig: valid config returns dataRoot (0.655765ms)
✔ findNewestSession: picks the newest by mtime among two files (0.503274ms)
✔ findNewestSession: finds files in nested subdirectories (0.310007ms)
✔ findNewestSession: returns null for a missing dir (0.126137ms)
✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.640449ms)
✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.359768ms)
✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.510907ms)
✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (1.137813ms)
✔ deriveState: full state table (0.165548ms)
✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.322908ms)
✔ rule: newest entry is a tool result with no assistant text after it is working (0.269971ms)
✔ rule: a finished ordinary turn is idle, even if it says your move (0.252313ms)
✔ task: the first user message of the session, from text blocks (0.255779ms)
✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.259583ms)
✔ task: no user message in the log means null (shown as unknown), never a guess (0.240526ms)
✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.33869ms)
✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.454611ms)
✔ scan: the written record carries task, workspace and activeProject (0.423141ms)
✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.533018ms)
✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.340643ms)
✔ registration: a record whose pid is gone is stale; derived values win, sources say derived, registered stays with alive false; a pid the probe cannot decide is not stale; pidAlive itself (0.88427ms)
✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.373513ms)
✔ loadRegistrations: a missing seatsDir gives empty lists (0.167388ms)
✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (0.653707ms)
✔ matchRegistration: matches by sessionsDir, and by realpath through a symlink; sessionsDir null never matches; same seat name with a different sessionsDir does not match (fleet vs repo darkwing) (0.296212ms)
✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (0.587071ms)
✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.687735ms)
✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.45922ms)
✔ scanAgent: ageSeconds is computed from the injected now (0.234589ms)
✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.137337ms)
✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.285456ms)
✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.39289ms)
✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (0.749603ms)
✔ scan: relative boardDir throws ConfigError (0.08125ms)
✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (53.748453ms)
✔ CLI: missing config exits 2 with a refused: message (52.838569ms)
✔ CLI: unknown command exits 2 (50.517081ms)
✔ CLI: unknown --liveness value exits 2 (49.256105ms)
✔ panesRunPi: true when any trimmed line equals 'pi' (0.195637ms)
✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.069057ms)
✔ tmuxIsAlive: a pane running pi is alive (0.156437ms)
✔ tmuxIsAlive: session exists but pi has exited is not alive (0.072861ms)
✔ tmuxIsAlive: no such tmux session is not alive (0.050394ms)
✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.059031ms)
✔ tmuxIsAlive: passes -L <socket> only when a socket is given (0.092924ms)
✔ parsePanes: one pane per line, command and optional tab-separated path (0.079341ms)
✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.070498ms)
✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.09033ms)
✔ loadSeen: missing file returns {} (0.18902ms)
✔ loadSeen: invalid JSON throws ConfigError (0.212722ms)
✔ loadSeen: a JSON array throws ConfigError (0.154707ms)
✔ loadSeen: a non-string value throws ConfigError (0.161107ms)
✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.307616ms)
✔ markSeen: seen false deletes the key (0.233311ms)
✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.207346ms)
✔ markSeen: project containing '/' throws ConfigError (0.115812ms)
✔ markSeen: non-boolean seen throws ConfigError (0.10141ms)
✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.261188ms)
✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.22145ms)
✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.242863ms)
✔ scanAgent: an error-state session with a matching mark is seen (0.221702ms)
✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.467473ms)
✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.174092ms)
✔ taskSetBy: a registered task carries the record's setter; a record without the field (pre-#1511) reads unknown; the value is not copied into registered (0.591007ms)
✔ taskSetBy: null whenever the task shown is not the registered one: no registration, an empty registered task, a stale registration; the field is always present (0.552957ms)
✔ taskSetBy: scan() reads the field from disk through the seat package (bounded there), writes it to the per-agent record and index, and an invalid on-disk value is a registrationError, never a row value (0.927347ms)
✔ isLoopbackHost: recognizes loopback hosts (1.136913ms)
✔ isLoopbackHost: rejects non-loopback hosts (3.98672ms)
✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (2.91531ms)
✔ startServer: serves page, healthz, and a rescanning /api/board (30.887042ms)
✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (5.176596ms)
✔ startServer: /api/board returns 500 JSON with an error field when scan throws (2.067307ms)
✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (50.952826ms)
✔ CLI: serve rejects a non-numeric --port with exit 2 (53.249748ms)
✔ CLI: scan still works after the async cli refactor (53.22716ms)
✔ CLI: live serve prints its URL and answers /healthz (63.739063ms)
✔ page.html: esc() escapes every HTML-significant character (1.046684ms)
✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (8.351111ms)
✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (3.230604ms)
✔ POST /api/seen with invalid JSON returns 400 (2.191135ms)
✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (2.497317ms)
✔ POST /api/seen with a missing agent returns 400 (2.002926ms)
✔ POST /api/board returns 405; PUT /api/seen returns 405 (2.145403ms)
✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (54.741614ms)
✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.334991ms)
✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.309151ms)
✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.184981ms)
✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.157765ms)
✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.31252ms)
✔ page.html: task and active project cells show their source via sourceTag(); the detail has a Registered row via registeredText(); SOURCE_LABEL maps registration to registered; every dynamic value in sourceTag/fromSource/registeredText is escaped (0.618644ms)
✔ POST /api/reply: runs agent-send.sh with -s from the registration, -S <host>:control-board, -m text plus the fixed trailer, no -L on the default socket, MOSAIC_TMUX_SOCKET stripped; answers delivered with the exit code and both streams (32.706831ms)
✔ POST /api/reply: a registration with a tmux socket adds -L <socket> (29.678616ms)
✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (27.912241ms)
✔ POST /api/reply: refusals before the tool runs: empty or blank or long text 400, unknown row 404, no registration 409, stale registration 409, no tmux session 409, bad JSON 400; the tool is never called (16.019508ms)
✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (5.496797ms)
✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.194523ms)
✔ page.html: the reply box appears only where canReply() holds (live registration with a tmux session), the detail has a Reply row, the submit posts JSON to /api/reply, receipts and drafts survive a refresh, and every receipt value is escaped (0.628604ms)
✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (2.720726ms)
✔ page.html: the task cell and detail show who set a registered task via setByTag()/setByText(), both escaped, only from rec.taskSetBy; the reply gate does not read it (0.31026ms)
✔ Host/Origin guard: GET /api/board and POST /api/reply refuse a foreign Host, a wrong port and a cross-origin Origin with 403 JSON, before any scan or send, and never send CORS headers (10.582797ms)
✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (32.911417ms)
✔ conversation routes (F16): a foreign Host, a wrong port and a cross-origin Origin get 403 before the reader runs, with no CORS headers (7.127992ms)
✔ every refusal code the reader can raise has an HTTP status (1.447595ms)
✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (59.446544ms)
ℹ tests 124
ℹ suites 0
ℹ pass 124
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1283.867383
@@ -0,0 +1,190 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (128.785295ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (7.501402ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (4.045852ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (127.65419ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (191.627471ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (134.726541ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (19.47196ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (15.816745ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.661486ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (5922.87706ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (25122.036615ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (159.106132ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (102.776695ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (287.631416ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (212.09447ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (235.226198ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (36.722154ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (117.431313ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (34.35896ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (100.385078ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (25.170254ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (61.622545ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (59.580643ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.15187ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.260028ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (2.168042ms)
✔ no shim from this file's tests is left running for the after hook (6.769396ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (286.980245ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (154.155109ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (281.135157ms)
✔ K4: two engines; force stop one; the other survives by independent observation (2309.924102ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (200.048741ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (308.831133ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (217.276899ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4425.035389ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (447.645633ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (376.962548ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (270.485921ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (64.649087ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (33.400498ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (44.587852ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3027.905936ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (4.882583ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (31.87394ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (28.068983ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (85.432079ms)
✔ R1: after a proven force stop the controller releases the scope; the shim and the unit are gone (195.040466ms)
✔ R2: close releases the scope of a binding proven stopped, once, when the stop's own release hasn't run (309.231358ms)
✔ R3: no release after an unavailable stop or an unverified proof: the scope and its shim stay as evidence (737.576481ms)
✔ E1: a natural engine exit with no member left: an engine-exit stop, stopped on a proof naming the engine, and the scope released (205.855468ms)
✔ E2: an engine exit with a tool child alive: the stop ends uncertain and nothing is released; a force stop then ends it (2263.385627ms)
✔ E3: an engine exit and a force stop take one escalation slot, in either order (645.671679ms)
✔ E4: a shim that doesn't answer: the engine-exit stop misses its deadline, ends uncertain and frees the slot (699.834651ms)
✔ E5: an engine exit after a force stop ended uncertain starts no engine-exit stop; the force stop stays current (2446.555171ms)
✔ E6: the engine-exit observation refuses a reaped process that isn't the recorded engine: another PID or start ticks (189.030458ms)
✔ R5: the harness finds a running shim and reaps it, and never signals a shim outside a mosaic-chat- scope (Filbert N1 and N2 on #1533) (397.784447ms)
✔ R6: releaseCohort releases nothing while the engine runs, for another invocation ID, or for a cohort without a scope (134.003024ms)
✔ R7: controller killed after the claim is stopped, before the release: the restart releases the scope (2359.66517ms)
✔ R8: controller killed between the two keys' stopped revisions: the restart releases the scope (256.14461ms)
✔ R9: a release that ended unavailable is tried again on a confirmed recover, not a refused one (213.726874ms)
✔ R10: close releases and signals nothing for a stopped binding whose proof no longer verifies (Filbert N3 on #1536) (195.731648ms)
✔ R11: close({ killEngine: true }) after a proven stop sends no signal to the recorded engine PID; a restart releases nothing more (2283.904952ms)
✔ R12: a restart onto a claim stopped on a boot proof releases nothing, though its scope is listed (173.201859ms)
✔ no shim from this file's tests is left running for the after hook (6.081796ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (31.589628ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (21.47064ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (15.470576ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (27.061272ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (14.310895ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.397788ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (15.552114ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (120.747704ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (37.81147ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (15.275417ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (27.015583ms)
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (24.136558ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.739011ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (24.77177ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (40.205933ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (33.939341ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (19.850611ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.412471ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.269043ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.313492ms)
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.486233ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.209405ms)
✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.511ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.878604ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.472922ms)
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.353932ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.107185ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (205.45317ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (39.887732ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (74.335136ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (123.355447ms)
✔ H4: self-takeover is refused (13.797372ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (81.242459ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (70.33587ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (18.873513ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (68.602324ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (128.735851ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (15.245785ms)
✔ H13: a retry with the same request ID and different text is refused (13.48888ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (133.083217ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (67.143985ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (17.46898ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (56.011651ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (22.439209ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (54.463473ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (14.086706ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (118.059955ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.695536ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (468.818598ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (359.064959ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (326.576468ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2413.639817ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (469.903704ms)
✔ no shim from this file's tests is left running for the after hook (6.368734ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (5.711992ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.551619ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.554124ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.210776ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.293083ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.656915ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.661976ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.060875ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (6.316356ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (3.957573ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.013926ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (3.979053ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (6.231957ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.137318ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.472996ms)
✔ F9: registrations never add or redirect a root (1.448789ms)
✔ F10: a header cwd naming another project is refused (3.740997ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.675387ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (3.455416ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (1.742228ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.175212ms)
✔ F13: compaction is a marker in place, then the retained content (0.667152ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (489.026588ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.536646ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.542255ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.164936ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.044583ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.768173ms)
✔ every page and cursor is a valid CHAT-01 record (713.702799ms)
✔ the engine pin holds for the installed package (1.734826ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (239.76786ms)
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (478.585097ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (239.886179ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (59.130738ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (29.352805ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (16.733231ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.247928ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (23.915039ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (19.272247ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (113.82729ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (35.679668ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1529.522081ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (14.325818ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (70.287524ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (15.549196ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (16.422768ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (32.704942ms)
✔ N10: fake conformance (31.22561ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (30.529752ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (20.899616ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (66.139469ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (29.93512ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (31.285228ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (29.531431ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (16.98411ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (33.770099ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (119.209662ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (141.545796ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (91.271127ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (21.664131ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (17.905369ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (18.05507ms)
✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (47.137312ms)
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (36.041072ms)
ℹ tests 182
ℹ suites 0
ℹ pass 182
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 77907.718753
@@ -0,0 +1,211 @@
✔ sessionModel: agent vars win, then the system's execution settings (8.168026ms)
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (3.468791ms)
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (1.867555ms)
✔ the prompt's built-in tools line is the harness's own names, as the manifest lists them (6.597892ms)
✔ the prompt names the project clones in the workspace, and none when there are none (4.283634ms)
✔ the prompt explains the review loop to a role with push_working, and reviews to one with fetch_review (5.056082ms)
✔ a bundle is written once: an existing file refuses (1.122933ms)
✔ a path with a single quote can't go into the hook command (1.350921ms)
✔ allow exits 0, a deny exits 2 with the reason on stderr (103.262138ms)
✔ a missing or wrong policy, or a bad event, exits 2 (80.472006ms)
✔ the bundle's wrapped command: a missing gate or node still blocks (1082.988083ms)
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (696.331276ms)
✔ claude: the hook alone blocks a path outside the workspace (406.874491ms)
✔ claude: a second turn resumes the first turn's session (703.495429ms)
✔ claude adapter: --restricted is always passed (5.064352ms)
✔ claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable (18.693934ms)
✔ claude adapter: a directory pin exits 2 before it runs; a pin with a space runs (7.780854ms)
✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (757.047567ms)
✔ claude: a missing hook or MCP file refuses before claude starts (8.332149ms)
✔ pi: policy tools and typed tools pass, anything else is blocked (2.648416ms)
✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.605139ms)
✔ file tool paths must resolve inside the workspace (0.919548ms)
✔ pi's own path normalisation can't be used to step out (0.769313ms)
✔ a symlink inside the workspace that points out is outside (0.631131ms)
✔ a dangling symlink is refused at any depth, in both harnesses (2.289178ms)
✔ read is checked under every spelling pi's read would open, in both harnesses (11.540262ms)
✔ other spellings cover directories, dangling links and pi's cwd (1.464126ms)
✔ a relative path climbs from the workspace's real path, in both harnesses (3.672073ms)
✔ claude path fields per tool (0.605885ms)
✔ glob patterns stay inside the workspace (0.562484ms)
✔ a path that can't be checked is blocked (0.354909ms)
✔ fetch_review's have lists the working refs before origin's, and no more than HAVE_MAX (184.84487ms)
✔ fetch_review's have orders the working refs newest first within each clone, one clone after another (133.418103ms)
✔ a runner git past its time: SIGTERM reaches its process group, and SIGKILL follows after the grace period (1810.578134ms)
✔ once a signalled runner git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (303.758884ms)
✔ initialize, ping and tools/list (34.02771ms)
✔ tools/call goes through the tool socket; a refusal is an isError result (29.45861ms)
✔ replies are compact JSON; over the cap a tool error names the size and the cap (33.356564ms)
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (29.65213ms)
✔ a missing argument is a usage error (24.461637ms)
✖ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (11.63517ms)
✖ pi: a reply over the cap is a tool error naming the size and the cap; list_tasks asks for open tasks (3.442663ms)
✖ pi: a write through a dangling symlink is blocked, and nothing appears outside (1.875445ms)
✖ pi: a read is refused when pi would open another spelling outside (1.602109ms)
✖ pi: a relative path climbs from the real path of a workspace behind a symlink (1.618577ms)
✖ pi: a relative path climbs from the real path of a dataRoot behind a symlink (1.642946ms)
✖ pi: a missing extension refuses before any model call (1.509153ms)
✖ pi: an extension without its configuration fails pi's start (1.36882ms)
✔ founderCheck: founder variables, then a needed service without a usable token (1.008021ms)
✔ turnRequest names the sender, class, reply and decision (0.159275ms)
✔ turnRequest says a tester's message is a test and never Jason's instruction (0.185449ms)
✔ turnRequest shows the message's own sent time, a day old as well as new (0.137153ms)
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.187658ms)
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (151.046888ms)
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (167.956836ms)
✔ a task closed on the tracker is missing from the next turn's open list (169.133259ms)
✔ a message read a day after it was sent shows the time it was sent (88.788867ms)
✔ a decision the human resolves becomes a turn citing the decision and the answer (140.097611ms)
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (63.167868ms)
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (54.868212ms)
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (271.273677ms)
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1197.434566ms)
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (126.019891ms)
✔ founder credentials stop before the claim (20) (112.482337ms)
✔ a refused claim exits 21; an ended run's capability exits 22 (123.073494ms)
✔ the launch ending under a running session exits 22 (110.847666ms)
✔ a broker that stays unreachable exits 23 after brokerRetries polls (219.098974ms)
✔ a broker that is down at the claim exits 23, not 21 (67.915658ms)
✔ no capability, or a malformed one, on stdin exits 2 (197.408099ms)
✔ a missing or malformed policy exits 2 before the claim (111.898351ms)
✔ the PM gets launch, its task verbs and the reads (6.210887ms)
✔ list_requirements reads the requirement ids, for a task.create holder with a tracker (3.868419ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (1.170383ms)
✔ a coder with a tracker gets its own task tools and no task_reassign (1.026278ms)
✔ launch only when the business's launch block names the instance as launcher (0.952137ms)
✔ an action outside the instance's authority has no tool (0.993193ms)
✔ the review loop: push_working and request_review for the coder, fetch_review and review_verdict for the reviewer, none for the PM (3.783169ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (6.731688ms)
✔ list_tasks takes the broker's filters and pages; get_task takes a task_ref (1.916931ms)
✔ callTool sends list_tasks done false unless the session names done; other tools as given (1.901888ms)
✔ replyText: compact JSON up to the cap, an error naming the size and the cap over it (0.604045ms)
ℹ tests 81
ℹ suites 0
ℹ pass 73
ℹ fail 8
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 17599.392947
✖ failing tests:
test at packages/harness/tests/pi-session.test.mjs:67:1
✖ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (11.63517ms)
Error [BusinessError]: /mnt/storage/scratch/tmp/mosaic-harness-biz-WaHaiD/businesses/acme.json launch: instance pm selects unknown model probe-model
at refuse (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/errors.mjs:12:9)
at checkLaunch (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:132:125)
at validateBusinessDocument (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:181:57)
at resolvedFor (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/helpers.mjs:27:20)
at session (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:26:20)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:68:33)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
exitCode: 2
}
test at packages/harness/tests/pi-session.test.mjs:102:1
✖ pi: a reply over the cap is a tool error naming the size and the cap; list_tasks asks for open tasks (3.442663ms)
Error [BusinessError]: /mnt/storage/scratch/tmp/mosaic-harness-biz-yTEg0Q/businesses/acme.json launch: instance pm selects unknown model probe-model
at refuse (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/errors.mjs:12:9)
at checkLaunch (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:132:125)
at validateBusinessDocument (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:181:57)
at resolvedFor (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/helpers.mjs:27:20)
at session (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:26:20)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:103:28)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
exitCode: 2
}
test at packages/harness/tests/pi-session.test.mjs:126:1
✖ pi: a write through a dangling symlink is blocked, and nothing appears outside (1.875445ms)
Error [BusinessError]: /mnt/storage/scratch/tmp/mosaic-harness-biz-DyVqIz/businesses/acme.json launch: instance pm selects unknown model probe-model
at refuse (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/errors.mjs:12:9)
at checkLaunch (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:132:125)
at validateBusinessDocument (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:181:57)
at resolvedFor (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/helpers.mjs:27:20)
at session (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:26:20)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:127:38)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
exitCode: 2
}
test at packages/harness/tests/pi-session.test.mjs:150:1
✖ pi: a read is refused when pi would open another spelling outside (1.602109ms)
Error [BusinessError]: /mnt/storage/scratch/tmp/mosaic-harness-biz-uuKz9o/businesses/acme.json launch: instance pm selects unknown model probe-model
at refuse (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/errors.mjs:12:9)
at checkLaunch (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:132:125)
at validateBusinessDocument (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:181:57)
at resolvedFor (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/helpers.mjs:27:20)
at session (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:26:20)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:161:38)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
exitCode: 2
}
test at packages/harness/tests/pi-session.test.mjs:211:3
✖ pi: a relative path climbs from the real path of a workspace behind a symlink (1.618577ms)
Error [BusinessError]: /mnt/storage/scratch/tmp/mosaic-harness-biz-jIOToM/businesses/acme.json launch: instance pm selects unknown model probe-model
at refuse (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/errors.mjs:12:9)
at checkLaunch (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:132:125)
at validateBusinessDocument (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:181:57)
at resolvedFor (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/helpers.mjs:27:20)
at session (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:26:20)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:212:40)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
exitCode: 2
}
test at packages/harness/tests/pi-session.test.mjs:211:3
✖ pi: a relative path climbs from the real path of a dataRoot behind a symlink (1.642946ms)
Error [BusinessError]: /mnt/storage/scratch/tmp/mosaic-harness-biz-eLp8m1/businesses/acme.json launch: instance pm selects unknown model probe-model
at refuse (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/errors.mjs:12:9)
at checkLaunch (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:132:125)
at validateBusinessDocument (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:181:57)
at resolvedFor (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/helpers.mjs:27:20)
at session (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:26:20)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:212:40)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
exitCode: 2
}
test at packages/harness/tests/pi-session.test.mjs:242:1
✖ pi: a missing extension refuses before any model call (1.509153ms)
Error [BusinessError]: /mnt/storage/scratch/tmp/mosaic-harness-biz-izCzMw/businesses/acme.json launch: instance pm selects unknown model probe-model
at refuse (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/errors.mjs:12:9)
at checkLaunch (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:132:125)
at validateBusinessDocument (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:181:57)
at resolvedFor (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/helpers.mjs:27:20)
at session (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:26:20)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:243:32)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
exitCode: 2
}
test at packages/harness/tests/pi-session.test.mjs:250:1
✖ pi: an extension without its configuration fails pi's start (1.36882ms)
Error [BusinessError]: /mnt/storage/scratch/tmp/mosaic-harness-biz-m6Aiu6/businesses/acme.json launch: instance pm selects unknown model probe-model
at refuse (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/errors.mjs:12:9)
at checkLaunch (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:132:125)
at validateBusinessDocument (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:181:57)
at resolvedFor (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/helpers.mjs:27:20)
at session (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:26:20)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/harness/tests/pi-session.test.mjs:251:27)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
exitCode: 2
}
@@ -0,0 +1,89 @@
✔ sessionModel: agent vars win, then the system's execution settings (7.818791ms)
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (3.958461ms)
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.006285ms)
✔ the prompt's built-in tools line is the harness's own names, as the manifest lists them (6.767726ms)
✔ the prompt names the project clones in the workspace, and none when there are none (4.321076ms)
✔ the prompt explains the review loop to a role with push_working, and reviews to one with fetch_review (5.229878ms)
✔ a bundle is written once: an existing file refuses (1.138055ms)
✔ a path with a single quote can't go into the hook command (1.178722ms)
✔ allow exits 0, a deny exits 2 with the reason on stderr (105.209197ms)
✔ a missing or wrong policy, or a bad event, exits 2 (75.667182ms)
✔ the bundle's wrapped command: a missing gate or node still blocks (1086.870053ms)
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (747.639312ms)
✔ claude: the hook alone blocks a path outside the workspace (437.905122ms)
✔ claude: a second turn resumes the first turn's session (712.601477ms)
✔ claude adapter: --restricted is always passed (5.580292ms)
✔ claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable (18.712325ms)
✔ claude adapter: a directory pin exits 2 before it runs; a pin with a space runs (6.03325ms)
✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (717.696021ms)
✔ claude: a missing hook or MCP file refuses before claude starts (8.027014ms)
✔ pi: policy tools and typed tools pass, anything else is blocked (2.436629ms)
✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.545655ms)
✔ file tool paths must resolve inside the workspace (0.907059ms)
✔ pi's own path normalisation can't be used to step out (0.797671ms)
✔ a symlink inside the workspace that points out is outside (0.728073ms)
✔ a dangling symlink is refused at any depth, in both harnesses (2.222052ms)
✔ read is checked under every spelling pi's read would open, in both harnesses (10.70842ms)
✔ other spellings cover directories, dangling links and pi's cwd (1.051642ms)
✔ a relative path climbs from the workspace's real path, in both harnesses (3.527694ms)
✔ claude path fields per tool (0.560541ms)
✔ glob patterns stay inside the workspace (0.485835ms)
✔ a path that can't be checked is blocked (0.294007ms)
✔ fetch_review's have lists the working refs before origin's, and no more than HAVE_MAX (175.213151ms)
✔ fetch_review's have orders the working refs newest first within each clone, one clone after another (125.803885ms)
✔ a runner git past its time: SIGTERM reaches its process group, and SIGKILL follows after the grace period (1810.345907ms)
✔ once a signalled runner git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (306.760621ms)
✔ initialize, ping and tools/list (35.641176ms)
✔ tools/call goes through the tool socket; a refusal is an isError result (28.433447ms)
✔ replies are compact JSON; over the cap a tool error names the size and the cap (36.25454ms)
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (27.383742ms)
✔ a missing argument is a usage error (25.253669ms)
✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (337.882943ms)
✔ pi: a reply over the cap is a tool error naming the size and the cap; list_tasks asks for open tasks (314.761128ms)
✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (321.01008ms)
✔ pi: a read is refused when pi would open another spelling outside (318.063812ms)
✔ pi: a relative path climbs from the real path of a workspace behind a symlink (313.87451ms)
✔ pi: a relative path climbs from the real path of a dataRoot behind a symlink (315.529524ms)
✔ pi: a missing extension refuses before any model call (6.587803ms)
✔ pi: an extension without its configuration fails pi's start (280.495828ms)
✔ founderCheck: founder variables, then a needed service without a usable token (0.977707ms)
✔ turnRequest names the sender, class, reply and decision (0.165136ms)
✔ turnRequest says a tester's message is a test and never Jason's instruction (0.194422ms)
✔ turnRequest shows the message's own sent time, a day old as well as new (0.118702ms)
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.191277ms)
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (151.877608ms)
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (170.374365ms)
✔ a task closed on the tracker is missing from the next turn's open list (167.916173ms)
✔ a message read a day after it was sent shows the time it was sent (89.418575ms)
✔ a decision the human resolves becomes a turn citing the decision and the answer (141.776777ms)
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (64.117418ms)
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (49.325445ms)
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (272.981431ms)
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1191.823731ms)
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (94.329678ms)
✔ founder credentials stop before the claim (20) (93.650679ms)
✔ a refused claim exits 21; an ended run's capability exits 22 (99.618949ms)
✔ the launch ending under a running session exits 22 (105.158034ms)
✔ a broker that stays unreachable exits 23 after brokerRetries polls (207.676602ms)
✔ a broker that is down at the claim exits 23, not 21 (46.942533ms)
✔ no capability, or a malformed one, on stdin exits 2 (156.480452ms)
✔ a missing or malformed policy exits 2 before the claim (87.86403ms)
✔ the PM gets launch, its task verbs and the reads (6.088115ms)
✔ list_requirements reads the requirement ids, for a task.create holder with a tracker (3.626245ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (1.182533ms)
✔ a coder with a tracker gets its own task tools and no task_reassign (1.040036ms)
✔ launch only when the business's launch block names the instance as launcher (0.983847ms)
✔ an action outside the instance's authority has no tool (0.975084ms)
✔ the review loop: push_working and request_review for the coder, fetch_review and review_verdict for the reviewer, none for the PM (3.958918ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (6.993845ms)
✔ list_tasks takes the broker's filters and pages; get_task takes a task_ref (1.928614ms)
✔ callTool sends list_tasks done false unless the session names done; other tools as given (1.906119ms)
✔ replyText: compact JSON up to the cap, an error naming the size and the cap over it (0.611221ms)
ℹ tests 81
ℹ suites 0
ℹ pass 81
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 19769.962427
@@ -0,0 +1,94 @@
✔ a raw token file, with or without one trailing newline, reaches curl only through the config stream (123.37792ms)
✔ the raw path accepts nothing else, and refuses before curl runs (382.30632ms)
✔ the file checks still apply on the raw path: mode, symlink, missing, directory (279.615075ms)
✔ the raw path base URL has no override (58.875076ms)
✔ the JSON path is unchanged, and JSON never falls through to the raw path (246.894533ms)
✔ a file that changes between the two reads refuses before curl runs, with or without a body (711.523072ms)
✔ the token reaches no child environment, even with an inherited CFG or SHELLOPTS=allexport (692.970545ms)
✔ real helper GET HTTP 200 preserves exit 0 without credentials (17.045287ms)
✔ real helper POST HTTP 201 preserves exit 0 without credentials (11.892058ms)
✔ real helper GET HTTP 403 preserves exit 1 without credentials (9.797874ms)
✔ fixture git subjects only, follow-ups and three session kinds (108.197829ms)
✔ text and JSON carry same numbers, open and truncated title (177.417933ms)
✔ missing credentials exit 2, no-issues never calls API and shows unknown (138.506559ms)
✔ empty range gives no rows and zero totals (100.981912ms)
✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (111.506391ms)
✔ close-only issue included, even median, missing metadata stays unknown (101.039364ms)
✔ unique commits but per-issue links count multiple tags once each (101.368301ms)
✔ issue pages of 50, 50 and 7 read 107 issues in 3 pages (292.49288ms)
✔ one short page reads 1 page; --no-issues reads none and says so (200.29336ms)
✔ a short page 10 is read; a full page 10 refuses at the ceiling (674.027271ms)
✔ an issue number on two pages refuses (121.109099ms)
✔ a failing page 2 refuses the whole read without its stderr (113.937829ms)
✔ page 2 with a malformed record refuses (118.587004ms)
✔ page 2 with more than 50 rows refuses (115.787453ms)
✔ bad API payload not JSON refuses (92.30169ms)
✔ bad API payload {} refuses (91.394246ms)
✔ bad API payload [{"number":1}] refuses (89.559933ms)
✔ partial or malformed session log refuses with location, not content (101.874754ms)
✔ a U+2028 or U+2029 inside a session string is one line, not a malformed record (101.655912ms)
✔ no sessions is an empty table; symlink source refuses (165.079235ms)
✔ reads only refactor even when another branch is checked out (104.449653ms)
✔ invalid dates, reverse dates and duplicate options refuse (63.865815ms)
✔ T3 agent assignments do not count as human in Table 2 (106.852182ms)
✔ preamble parsing and issue number boundaries (0.54731ms)
✔ T3 header: agent, or board from control-board; anything short of the full header is human (0.154119ms)
✔ no closed issues with human messages means undefined ratio, not invented zero (0.205236ms)
✔ T3: seat, archived, unmapped and Researcher threads count; imported, deleted and other-project threads do not (200.913104ms)
✔ T3: the default path is read from HOME and prints no path line; --no-t3 says so (418.774499ms)
✔ T3: a HOME with no database exits 1 and names --no-t3 (95.977524ms)
✔ T3: a file that is not a database exits 1 and names --no-t3 (100.002614ms)
✔ T3: a seat thread renamed to another seat exits 1 naming thread, title and roles (120.187889ms)
✔ T3: an unmapped thread addressed as a seat exits 1 (124.910032ms)
✔ T3: a header to another thread id is not cross-checked (124.564553ms)
✔ T3: no project, or two, for this root exits 1 (307.802161ms)
✔ T3: a removed column exits 1 and names it (119.517879ms)
✔ T3: a missing table exits 1 and names it (113.365299ms)
✔ T3: a counted row with an unknown role exits 1 without its text (119.962024ms)
✔ T3: a counted row with role reasoning without the reasoning: id prefix exits 1 without its text (122.224398ms)
✔ T3: a counted row with non-text content exits 1 without its text (119.543402ms)
✔ T3: a counted row with an unparseable created_at exits 1 without its text (123.503581ms)
✔ T3: a reasoning summary is skipped before any check and counted as excluded (312.361041ms)
✔ T3: a missing orchestration_events makes the diagnostic unknown and keeps the counts (212.934483ms)
✔ T3: a human message with no event counts in humanWithoutEvent (120.021881ms)
✔ T3: an unparseable event makes the diagnostic unknown and keeps the counts (215.900187ms)
✔ T3: an event with no string messageId makes the diagnostic unknown and keeps the counts (217.498886ms)
✔ T3: an error that is not from SQLite is rethrown, not reported as a database failure (64.012897ms)
✔ T3: a symlink at ~/.t3 exits 1 (89.535883ms)
✔ T3: a symlink at ~/.t3/userdata exits 1 (89.449768ms)
✔ T3: a symlink at ~/.t3/userdata/state.sqlite exits 1 (92.576756ms)
✔ T3: with --t3-db, a symlinked file or directory exits 1 (171.398258ms)
✔ T3 WAL: the newest message only in -wal, writer attached, is counted (125.927814ms)
✔ T3 WAL: stopped cleanly, counts are correct and the main file is unchanged (135.451618ms)
✔ T3 WAL: -wal without -shm in a writable directory is read (146.617888ms)
✔ T3 WAL: -wal without -shm in a read-only directory exits 1 (144.474456ms)
✔ T3 WAL: stopped cleanly in a read-only directory exits 1 (123.799419ms)
✔ T3: a lock held past the 5 s busy timeout exits 1 and names --no-t3 (5125.24606ms)
✔ a done row whose closing issue is open is a violation; a row that is not done is not (1.940006ms)
✔ an issue several rows close is expected closed only once all of them are done (0.531751ms)
✔ closure needs positive evidence: unknown is undecided, and so is a skipped or short issue check (0.362485ms)
✔ each owner of an in-progress or in-review row gets one liveness class (7.967527ms)
✔ a required row not done after 14 days is a violation; a legacy row uses genesis as its lower bound (0.368263ms)
✔ an ISO requiredSince, as `set required` writes it, ages from its UTC day; one that does not parse is a violation (0.240271ms)
✔ the text section always ends in a count and a result, and never prints a full pass (0.322675ms)
✔ pidAlive: a running pid is present, an exited one is gone, and EPERM still means present (24.736297ms)
✔ issue states: open list first, then the metric page, then at most 10 lookups (285.312304ms)
✔ a full open list: lookups settle what it leaves out, and only an unsettled issue keeps it undecided (369.131828ms)
✔ the open list refuses on a failed call or a bad record, and never echoes the helper (171.638932ms)
✔ a helper call past the deadline is killed with its child, and the call reports it (2009.864394ms)
✔ readQueue loads queue.json through the queue validator and refuses anything else (92.627838ms)
✔ protected changes list every in-range entry that changes a required or parked row (262.64392ms)
✔ the CLI prints the queue section above the weekly table and under a queue key in --json (511.19307ms)
✔ a queue with nothing wrong prints 0 violations and a reduced pass, never a full pass (208.335657ms)
✔ --no-issues makes no call and leaves the issue checks undecided; --no-queue skips the section (280.702631ms)
✔ the CLI refuses a bad queue before any call, and a failed open list with exit 2 (254.41276ms)
✔ --unsupported-runtime repeats once per seat and takes a seat name (314.94377ms)
✔ an unreadable config makes every owner invalid instead of passing them (139.36013ms)
ℹ tests 86
ℹ suites 0
ℹ pass 86
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 20644.355848
@@ -0,0 +1,145 @@
✔ a first launch clones the root at its branch, with the root's path as origin (34.762772ms)
✔ the clone is not at its place until git clone has finished (24.392972ms)
✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (67.507913ms)
✔ a clone ahead of the root keeps its commits and counts as current (42.201437ms)
▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves
✔ a modified file (50.384168ms)
✔ an untracked file (50.349337ms)
✔ a staged file (50.439993ms)
✔ another branch (48.82057ms)
✔ a detached HEAD (48.576973ms)
✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (249.93949ms)
✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (50.569586ms)
▶ a clone kept before the branch check, or for a failed status, gets no fetch
✔ not-a-clone (29.013035ms)
✔ hooks-present (31.155339ms)
✔ submodule (40.261519ms)
✔ config-changed (38.451507ms)
✔ root-unreadable (33.102566ms)
✔ root-detached (37.862623ms)
✔ status-failed, on the root's branch and clean (42.163126ms)
✔ status-failed, dirty (40.986973ms)
✔ a clone kept before the branch check, or for a failed status, gets no fetch (294.484876ms)
✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (44.693348ms)
✔ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (52.290939ms)
✔ a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge (82.04286ms)
▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it
✔ user.name (30.396975ms)
✔ user.email (30.005667ms)
✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (60.936933ms)
✔ a commit git can't read after the fast-forward is null (57.418257ms)
✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (25.980558ms)
▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it
✔ core.fsmonitor (48.085258ms)
✔ core.hooksPath (39.547321ms)
✔ a hook file (37.168737ms)
✔ include.path (39.409975ms)
✔ includeIf (40.458444ms)
✔ a filter driver (40.964003ms)
✔ origin moved (40.172867ms)
✔ a second origin url (39.382989ms)
✔ the origin url twice, both the root (41.912462ms)
✔ an ext:: origin (40.168172ms)
✔ a branch tracking another remote (40.686981ms)
✔ a branch merging a tag (41.672265ms)
✔ a fetch line onto local branches (45.138665ms)
✔ no origin (40.438794ms)
✔ a config file over the size cap (38.140512ms)
✔ config a symlink to /dev/zero (37.94704ms)
✔ the clone a symlink to the bait (36.366921ms)
✔ the clone a file (37.654287ms)
✔ the clone a plain directory (37.07824ms)
✔ .git a gitdir: file (37.825589ms)
✔ .git a symlink (37.033947ms)
✔ .git/logs a symlink (37.571622ms)
✔ .git/refs/heads a symlink (38.068007ms)
✔ .git/objects a symlink (36.400729ms)
✔ a reflog hard-linked to a file outside (35.937711ms)
✔ HEAD a fifo (42.206739ms)
✔ a commondir file (36.229585ms)
✔ an alternates file (36.235378ms)
✔ a gitlink staged, no .gitmodules (50.611631ms)
✔ a gitlink and .gitmodules staged (52.084673ms)
✔ a gitlink to a repository with its own gitlink (61.234361ms)
✔ a gitlink and .gitmodules committed (56.278986ms)
✔ a gitlink and submodule.active in config (51.309571ms)
✔ a .git/modules with no gitlink (39.854539ms)
✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1416.525614ms)
▶ a root that can't be cloned refuses with the project id and leaves nothing behind
✔ missing (11.973295ms)
✔ not a repository (12.419977ms)
✔ a plain directory inside another repository (17.475556ms)
✔ unreadable (11.920103ms)
✔ on no branch (13.403861ms)
✔ missing an object (18.089899ms)
✔ a root that can't be cloned refuses with the project id and leaves nothing behind (86.292472ms)
✔ a clone whose root is on no branch, or gone, is kept and named (35.270044ms)
✔ no credential or forge remote of the root reaches a clone (54.944664ms)
✔ every project in the business file gets its own clone at its own branch (42.280157ms)
✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (516.579089ms)
✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1020.612382ms)
✔ a timeout signals git's process group, so a process git started dies with it (517.21997ms)
✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (516.732595ms)
✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (1.713811ms)
✔ sandboxProblem names a missing bwrap and a bwrap that fails (9.281003ms)
✔ every bind says why; the bundle and session file stay read-only over the run directory (1.297611ms)
✔ the host's git socket is bound only when given, read-only and before the writable binds (0.773336ms)
✔ the git identity is the instance's, at <instance>@<business>.mosaic.invalid, and sandboxArgv refuses without it (1.119561ms)
✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (1901.554686ms)
✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2762.626808ms)
✔ a confined session that runs tester-cli.mjs is refused as tester, and the same shim outside the sandbox binds (236.97772ms)
✔ resolveSeat: by name under --repo resolves the repo layout (1.255593ms)
✔ resolveSeat: by path resolves the fleet layout (0.322748ms)
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.771443ms)
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.635453ms)
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.804646ms)
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.29069ms)
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.190737ms)
✔ CLI launch: registers, execs the fake launch script, and passes args through (32.522791ms)
✔ CLI launch: --harness lands in the record (29.016832ms)
✔ CLI launch: the launch script's own exit code passes through (29.300911ms)
✔ CLI launch: relaunching a seat rewrites the one registration record (64.063432ms)
✔ CLI launch: omitting --task records an empty string, not null (28.472264ms)
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (85.438256ms)
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (136.64763ms)
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.474047ms)
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.687802ms)
✔ 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.80486ms)
✔ 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.221718ms)
✔ 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 (276.061984ms)
✔ REQ-LIMIT-1: family substring matching is no longer exported (0.737535ms)
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.728638ms)
✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.253899ms)
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.658932ms)
✔ session file and launch log: 0600, the session file written once (1.136586ms)
✔ endReason maps the runner's exit codes; a signal is killed (0.159886ms)
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.495976ms)
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (1.01627ms)
✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (240.892431ms)
✔ workRef and validBranch take only what a ref name can hold (1.139115ms)
✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (103.64255ms)
✔ a rewritten branch refuses non-fast-forward and the work ref stays (93.406115ms)
✔ a branch with no history in common with main refuses unrelated-history (50.81908ms)
✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (42.953755ms)
✔ main is fetched from the root again before each push, so a clone of a newer root pushes (107.037534ms)
✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (67.97108ms)
✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (65.955193ms)
✔ only a v2 bundle with the named branch is read (50.76779ms)
✔ a branch named main, or named for another task, moves only this task's work ref (82.120058ms)
✔ a record that throws moves the work ref back, or removes a new one (116.563588ms)
✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (328.351104ms)
✔ a root that can't be read refuses root-unreadable (33.484681ms)
✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2134.550969ms)
✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1029.78467ms)
✔ a working repository path that isn't a directory refuses (27.346715ms)
✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (108.963346ms)
✔ review of a project with no working repository refuses unknown-commit (14.536127ms)
✔ the runner's git tools hold the host's limits and branch rule (1.265638ms)
ℹ tests 132
ℹ suites 0
ℹ pass 132
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 15879.176534
@@ -0,0 +1,100 @@
✔ the boot config is checked before anything starts (12.939495ms)
✔ a business with no tracker entry refuses task verbs (10.215351ms)
✔ credential.expiring and .expired are recorded once per instance (22.865583ms)
✔ a token file that changes on disk records credential.changed (10.058617ms)
✔ autostart polls, reconciles and retries a startup the tracker was down for (34.035585ms)
✔ a refusal a restart must clear is not retried by the poll (7.113779ms)
✔ a poll that fires while two are queued is dropped (9.125266ms)
✔ close waits for a running verb and refuses one that has not started (37.50242ms)
✔ the bundled Vikunja is the pinned upstream image the runbook names (0.685362ms)
✔ every published port is on 127.0.0.1, and no secret is in the file (0.262815ms)
✔ the committed table loads frozen; its verbs are the broker task verbs (1.364965ms)
✔ a write names a verb the table has as its own key, not one it inherits (0.462961ms)
✔ the schema refuses any other shape (2.634625ms)
✔ the Field table in the slice 1 plan names the verbs each field is written by (0.598395ms)
✔ each verb refuses, asks for a decision and writes as the table and the role files say (143.12078ms)
✔ the list holds the PRD's requirement ids, in its order, at its version (1.330409ms)
✔ the committed list loads frozen (0.580302ms)
✔ the schema refuses any other shape (2.291123ms)
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (84.454655ms)
✔ the project list holds recorded project objects in the recorded list envelope (7.012049ms)
✔ the recorded task bodies pass the checks S3 applies to every read (0.551728ms)
✔ the client works against the fake over real HTTP with the platform fetch (94.208593ms)
✔ a correct install starts, and the first reconcile records tasks that already exist (22.103798ms)
✔ verbs refuse while a business is starting and after startup refused it (12.358145ms)
✔ startup refuses a token that can do more than its role needs (31.249429ms)
✔ startup refuses an unsupported version and flags an untested one (21.590274ms)
✔ startup refuses a board that the runbook did not install (25.944417ms)
✔ startup refuses a project the sync bot cannot read (6.42912ms)
✔ startup refuses a configured label the pm bot cannot see (7.439894ms)
✔ startup refuses an expired credential and a missing sync credential (13.435271ms)
✔ an unreachable tracker refuses with tracker-unavailable (6.891073ms)
✔ an edit in the UI is recorded once, with the fields that changed (73.523817ms)
✔ a move between open buckets is seen on the board, though updated does not change (36.156003ms)
✔ a person's comment is counted and a bot's is not (64.370885ms)
✔ the hourly reconcile catches a comment through comment_count (78.421175ms)
✔ a task closed in the UI leaves the open view with its done bucket (113.742077ms)
✔ a task that leaves the board is recorded as deleted, moved or out of reach (64.192387ms)
✔ a poll that read before a verb wrote does not overwrite the verb (35.283025ms)
✔ a tracker fault during a tick is reported and the next tick catches up (35.036956ms)
✔ a malformed answer refuses the tick with tracker-shape (11.725937ms)
✔ no token value reaches the database, the log or a refusal (45.129224ms)
✔ the first look at a task counts only comments inside the window (34.33865ms)
✔ with descendants off the adapter reads no project list and no child, as in v0 (40.852277ms)
✔ a three-level tree is read per project, and a project outside it is not (17.34619ms)
✔ a watch-only child keeps the business ready, and a failed child read refuses as transient (19.716976ms)
✔ a root without the installed buckets refuses with descendants on (6.533414ms)
✔ the project list refuses without read_all, and skips archived projects and saved filters (16.366636ms)
✔ descendants must be true or false (6.752431ms)
✔ a move inside the tree is a moved tombstone plus a snapshot under the new ref (59.370477ms)
✔ a child made after startup joins the tree at the next reconcile (53.308118ms)
✔ a child that leaves the tree has its tasks recorded missing, and the ledger keeps their history (57.93008ms)
✔ a child deleted or hidden between reconciles drops out at the next tick, and the root still refuses (82.094241ms)
✔ a reconcile that fails keeps the previous tree (32.201321ms)
✔ a closed task the cursor missed takes its own project's done bucket (31.982887ms)
✔ closed tasks read at startup take their own project's done bucket (9.130078ms)
✔ a closed task the cursor returns takes its own project's done bucket (34.579827ms)
✔ a task moved out of a child that leaves the tree at the same time is moved, not no-access (32.496281ms)
✔ a task in a bucket outside the five keeps its raw bucket id (30.205531ms)
✔ bucket 0 on a watch-only task never becomes a bucket or a state (79.195315ms)
✔ a child whose kanban view is deleted still refuses the tick, and the reconcile reads it watch-only (55.835367ms)
✔ a child dropped at a tick comes back at the next reconcile once the bot reads it again (34.263031ms)
✔ task.create needs a recorded human request and a requirement id (31.063883ms)
✔ task.create takes only an approved requirement id (18.702928ms)
✔ task.create cites the request a received message carries, not the message's id (14.046276ms)
✔ only labels named in the business file can be written (19.739237ms)
✔ task.schedule sets and clears a due date and relations (25.309948ms)
✔ assign and reassign move the role bots and record task.assigned (51.98631ms)
✔ task.update.assigned is for the assignee and records task.state (49.654829ms)
✔ a wrong expected digest records task.conflict and writes nothing (16.955346ms)
✔ a cross-role verb needs a resolved decision, used once (21.87719ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (21.895181ms)
✔ a task with pushed work closes only citing an approve verdict for its latest push, and a refusal writes nothing (27.397801ms)
✔ a lost answer is settled by a re-read and never retried (25.270444ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (35.087452ms)
✔ a task the sync bot cannot read refuses and records nothing (15.288889ms)
✔ verbs and polls for one business run one at a time (84.94132ms)
✔ a due date with milliseconds is written to the second (61.982374ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (37.158072ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (13.776414ms)
✔ a create whose final read fails succeeds and records task.created (33.116555ms)
✔ an edit between the last write and the final read shows as external on the next poll (36.242553ms)
✔ task.created is recorded when a later label write fails (13.160218ms)
✔ every verb acts in an installed child, on the child's own board (46.311697ms)
✔ task.create refuses a project outside the tree, a watch-only one and one that is not an id (25.563841ms)
✔ with descendants off, task.create takes only the root project (13.078948ms)
✔ a verb on a watch-only task, or a relation to one, refuses with project-not-installed (17.138205ms)
✔ a related task is read before authority: in another project than its ref names, task-moved; missing, task-not-found (23.736392ms)
✔ a task moved to another project of the tree refuses its old ref with task-moved and takes its new one (15.955465ms)
✔ a verb and a create in the child record the final read of the child's board (15.228615ms)
✔ a create in the child whose final read fails records the child's todo bucket (36.836704ms)
✔ a lost answer in the child is settled by a re-read of the child's board (22.822732ms)
✔ every related task is read before authority, not only the first (17.252143ms)
ℹ tests 92
ℹ suites 0
ℹ pass 92
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3367.053617
@@ -0,0 +1,149 @@
✔ 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 (2936.46848ms)
✔ 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 (5160.113941ms)
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (1117.923305ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (1230.96683ms)
✔ the README says a refused field table names the file or the key at fault, never a path (0.886823ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (2781.992981ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (59.703386ms)
✔ decisions route serves the roles and recent lists from the reader view (29.498354ms)
✔ credentials route serves the broker credential status from the reader view (9.731256ms)
✔ replies route serves the replies to one message from the reader view (26.988739ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (53.175621ms)
✔ bus refusals map to statuses and never carry a path or a stack (26.271469ms)
✔ humanCall speaks the human transport protocol without blocking the server (1645.751599ms)
✔ busReader takes --business, else the live bus host, else refuses (157.728493ms)
✔ humanCall kills a transport that runs past its timeout (528.059019ms)
✔ serve refuses a --business value that is not a business id (230.043003ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (973.712811ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2188.131636ms)
✔ conversation view: a newer session with no readable history keeps the marker (900.775925ms)
✔ conversation view: seats without history say so and offer no reply (682.449342ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (686.773153ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (51751.477942ms)
✔ messages HTTP read pages and preserves metadata; bad arguments refuse before a read; other reads unchanged (75.405984ms)
✔ 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 (954.850693ms)
✔ Messages nav count polls on Tasks and Board and clears on a Board refusal; messages 403 refuses local pages (1685.341916ms)
✔ copy writes the Console paths at the full id, read-only, and leaves an existing copy alone (587.640323ms)
✔ copy refuses a commit off refactor, an unknown one, a worktree and a bad copies directory (531.372817ms)
✔ copy writes nothing when an archived path is missing at the commit (188.042734ms)
✔ a copy that loses the rename race leaves no read-only temp directory behind (348.856837ms)
✔ a copy whose rename fails with no target refuses, writes no copy and leaves no temp directory (356.607411ms)
✔ a copy that meets a target holding files says exists: and moves nothing into it (350.927896ms)
✔ a copy that meets an empty target directory replaces it by rename, not by moving into it (351.566855ms)
✔ serve runs from a copy: /healthz 200, the config check, roles and RELEASE from the copy, the queue from --checkout (695.842035ms)
✔ serve refuses a --checkout that is not an absolute directory path (224.69692ms)
✔ consoleReads runs the queue read under queueRoot and reads RELEASE under root (49.882789ms)
✔ preview options are all three or none, and each is checked (6.379186ms)
✔ the CLI refuses a partial or invalid preview flag set at start (526.700182ms)
✔ the CLI with all three flags serves the preview mode (110.620917ms)
✔ a request for the public name needs the header equal to the user id, else 401 and nothing read (15.446418ms)
✔ the header name matches without regard to case, and the value compare is exact (3.253706ms)
✔ the public name with the header reads the bus views, and the Host and Origin are checked first (24.872198ms)
✔ in preview mode every write answers 403 read-only, from the public name and loopback, and reaches nothing (9.199418ms)
✔ loopback requests are unchanged in preview mode, and the bus views work with the board down (4.139645ms)
✔ a proxy that rewrites Host to the loopback port gets 403 on its first request, whatever the user (14.194931ms)
✔ a repeated Host is 400 before any other check, in preview mode and without it (6.349018ms)
✔ without the preview flags the public name is refused and writes still reach the board (3.889054ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.281636ms)
✔ credential state from the date alone, as the bus states it (0.13224ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (340.408388ms)
✔ queue: a refused read fails closed with the store's text, redacted (177.524053ms)
✔ queue: a store that fails to load, or a child that dies, gives a fixed message with no path or stack (739.384273ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (8.653435ms)
✔ business: missing or invalid file refuses with the module's text, redacted (6.508019ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (6.103561ms)
✔ settings: release, board, loopback address, notifier binding name only (103.215201ms)
✔ task fields: the committed table with no reads configured, GET only; a bad table refuses with no path (17.429816ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (659.268356ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (20790.594595ms)
✔ 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 (2241.476145ms)
✔ a sent receipt lists its replies as the page refreshes, oldest first and capped, and marks none delivered or read (11016.08513ms)
✔ startServer takes send only as a function and only with the preview mode (5.229073ms)
✔ the CLI refuses --send without the preview flags, and without a configured bus (356.936204ms)
✔ the CLI with --send and a configured bus sends through the human transport and logs one line (112.837016ms)
✔ without --send the route is read-only like every other write, and config says no send (7.314181ms)
✔ with --send every other write is still 403 read-only, and nothing reaches the board or bus (5.087457ms)
✔ config says send only on the public name with --send on (1.664219ms)
✔ a send without the signed-in user is 401 and is neither sent nor logged, loopback included (3.387943ms)
✔ each request check refuses before anything is sent, and logs the refusal without the body (11.613877ms)
✔ a send passes exactly to, body and in_reply_to, answers the id, and logs it (2.521875ms)
✔ a crafted role name cannot forge a log line (1.766539ms)
✔ a bus refusal is not sent, with its status; an outcome the bus could not confirm is unknown and never retried (5.693423ms)
✔ through the human transport the request is exactly the business, message.send and the keys (74.546824ms)
✔ against a real broker: the message is written once as a human send, and its refusals map (19.009327ms)
✔ loopback host and board origin fail closed (5.269612ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (68.201322ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (43.146382ms)
✔ unreachable board reports URL; CLI rejects unsupported options (335.477528ms)
✔ render prints the unit without the preview flags (8.890971ms)
✔ render carries every serve flag, the preview set included (7.253684ms)
✔ a partial preview set is refused (14.446438ms)
✔ a value the unit cannot carry is refused, for every flag (236.335431ms)
✔ the rendered unit names none of the human proof agent markers and sets no agent variable (17.828342ms)
✔ install into --dir with --no-reload writes the file whole, then reports unchanged (49.983065ms)
✔ install without --no-reload runs daemon-reload and nothing that starts the unit (20.880475ms)
✔ uninstall refuses while the unit is running, with or without --no-reload, and removes it once it is not (112.922295ms)
✔ status prints the unit state and GET /healthz on the installed port (166.074725ms)
✔ usage: an unknown verb or flag, and start or enable, exit 4 (57.966193ms)
✔ without --send, render is the template with its placeholders filled, byte for byte (27.48719ms)
✔ render --send with the preview set ends ExecStart with --send and changes nothing else (43.456606ms)
✔ --send without the whole preview set is refused and writes nothing (109.534677ms)
✔ install --send into --dir with --no-reload writes the sending unit, and status says whether it sends (201.937915ms)
✔ uninstall and status take no --send (30.070766ms)
✔ install refuses while the unit is running, in any state but inactive or failed, and changes nothing (224.376705ms)
✔ when the state read fails, install and uninstall refuse with one line and change nothing (57.773885ms)
✔ install needs --copy naming a copy that exists, and refuses outside the canonical checkout without --allow-worktree (51.486268ms)
✔ install refuses unless the bus unit runs from the same copy, naming both paths, and changes nothing (198.682561ms)
✔ render --copy runs the copy and reads the queue in the checkout; without --copy the unit is unchanged (15.724965ms)
✔ the uid header takes only an Authentik uid; the username header still takes a username (267.953527ms)
✔ a preview value cannot start with - or . (28.32794ms)
✔ status names the copy and whether refactor has moved past it, and says when the unit runs from no copy (372.989062ms)
✔ Settings form shows every scope and held role; gated save, exact payload, retained draft and no automatic retry (1017.458276ms)
2026-10-15T00:54:07.323Z settings user="jason" changed
✔ REQ-WEB-1 signed-in same-origin Settings write reaches exactly one human operation (16.213763ms)
✔ REQ-WEB-1 Settings writes require enabled send, named Host, identity, exact Origin and same-origin Fetch Metadata (6.173636ms)
✔ REQ-WEB-1 Settings validates JSON, size, revision and strict writable keys before broker call (4.653737ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.96681ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.439387ms)
✖ shipped strings are unchanged, and the web keeps its write boundary (3.472041ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5432.864335ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2611.992641ms)
✔ Queue, Business and Settings in their five states, with no move button (3006.229456ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1127.178669ms)
✔ the server log names no config path when the system config refuses (312.740449ms)
ℹ tests 112
ℹ suites 0
ℹ pass 111
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 135408.0237
✖ failing tests:
test at packages/webui/tests/shell.test.mjs:37:1
✖ shipped strings are unchanged, and the web keeps its write boundary (3.472041ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
+ "fetch('/api/settings',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(payload),cache:'no-store',redirect:'manual'});",
"fetch('/api/bus/message', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ to, body: text }), cache: 'no-store', redirect: 'manual' });"
]
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/webui/tests/shell.test.mjs:57:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [ "fetch('/api/settings',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(payload),cache:'no-store',redirect:'manual'});", "fetch('/api/bus/message', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ to, body: text }), cache: 'no-store', redirect: 'manual' });" ],
expected: [ "fetch('/api/bus/message', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ to, body: text }), cache: 'no-store', redirect: 'manual' });" ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -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 (2863.950841ms)
✔ 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 (4864.831584ms)
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (1097.656001ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (1227.543634ms)
✔ the README says a refused field table names the file or the key at fault, never a path (0.774443ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (2782.645471ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (60.43979ms)
✔ decisions route serves the roles and recent lists from the reader view (29.511702ms)
✔ credentials route serves the broker credential status from the reader view (8.842728ms)
✔ replies route serves the replies to one message from the reader view (26.939026ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (54.182902ms)
✔ bus refusals map to statuses and never carry a path or a stack (25.179974ms)
✔ humanCall speaks the human transport protocol without blocking the server (1646.554502ms)
✔ busReader takes --business, else the live bus host, else refuses (151.868756ms)
✔ humanCall kills a transport that runs past its timeout (528.758312ms)
✔ serve refuses a --business value that is not a business id (224.287754ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (987.665612ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1313.685564ms)
✔ conversation view: a newer session with no readable history keeps the marker (894.880222ms)
✔ conversation view: seats without history say so and offer no reply (631.331401ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (632.107231ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (52071.125014ms)
✔ messages HTTP read pages and preserves metadata; bad arguments refuse before a read; other reads unchanged (76.491612ms)
✔ 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 (893.797452ms)
✔ Messages nav count polls on Tasks and Board and clears on a Board refusal; messages 403 refuses local pages (1617.331849ms)
✔ copy writes the Console paths at the full id, read-only, and leaves an existing copy alone (610.982136ms)
✔ copy refuses a commit off refactor, an unknown one, a worktree and a bad copies directory (533.749186ms)
✔ copy writes nothing when an archived path is missing at the commit (185.823072ms)
✔ a copy that loses the rename race leaves no read-only temp directory behind (343.228509ms)
✔ a copy whose rename fails with no target refuses, writes no copy and leaves no temp directory (343.098285ms)
✔ a copy that meets a target holding files says exists: and moves nothing into it (343.996734ms)
✔ a copy that meets an empty target directory replaces it by rename, not by moving into it (341.418762ms)
✔ serve runs from a copy: /healthz 200, the config check, roles and RELEASE from the copy, the queue from --checkout (659.034186ms)
✔ serve refuses a --checkout that is not an absolute directory path (233.89929ms)
✔ consoleReads runs the queue read under queueRoot and reads RELEASE under root (50.511818ms)
✔ preview options are all three or none, and each is checked (6.284519ms)
✔ the CLI refuses a partial or invalid preview flag set at start (521.45779ms)
✔ the CLI with all three flags serves the preview mode (111.378664ms)
✔ a request for the public name needs the header equal to the user id, else 401 and nothing read (15.704123ms)
✔ the header name matches without regard to case, and the value compare is exact (3.860039ms)
✔ the public name with the header reads the bus views, and the Host and Origin are checked first (24.541682ms)
✔ in preview mode every write answers 403 read-only, from the public name and loopback, and reaches nothing (8.978146ms)
✔ loopback requests are unchanged in preview mode, and the bus views work with the board down (4.298663ms)
✔ a proxy that rewrites Host to the loopback port gets 403 on its first request, whatever the user (14.038561ms)
✔ a repeated Host is 400 before any other check, in preview mode and without it (6.289714ms)
✔ without the preview flags the public name is refused and writes still reach the board (4.297741ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.195968ms)
✔ credential state from the date alone, as the bus states it (0.138527ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (334.253616ms)
✔ queue: a refused read fails closed with the store's text, redacted (178.141087ms)
✔ queue: a store that fails to load, or a child that dies, gives a fixed message with no path or stack (728.521995ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (8.729596ms)
✔ business: missing or invalid file refuses with the module's text, redacted (6.403225ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (6.900914ms)
✔ settings: release, board, loopback address, notifier binding name only (105.145946ms)
✔ task fields: the committed table with no reads configured, GET only; a bad table refuses with no path (17.744641ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (647.676352ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (20660.09197ms)
✔ 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 (2208.251741ms)
✔ a sent receipt lists its replies as the page refreshes, oldest first and capped, and marks none delivered or read (11007.815608ms)
✔ startServer takes send only as a function and only with the preview mode (5.189301ms)
✔ the CLI refuses --send without the preview flags, and without a configured bus (359.49865ms)
✔ the CLI with --send and a configured bus sends through the human transport and logs one line (113.151166ms)
✔ without --send the route is read-only like every other write, and config says no send (6.759773ms)
✔ with --send every other write is still 403 read-only, and nothing reaches the board or bus (5.071655ms)
✔ config says send only on the public name with --send on (1.78012ms)
✔ a send without the signed-in user is 401 and is neither sent nor logged, loopback included (3.559391ms)
✔ each request check refuses before anything is sent, and logs the refusal without the body (11.613913ms)
✔ a send passes exactly to, body and in_reply_to, answers the id, and logs it (2.570758ms)
✔ a crafted role name cannot forge a log line (2.02274ms)
✔ a bus refusal is not sent, with its status; an outcome the bus could not confirm is unknown and never retried (5.681448ms)
✔ through the human transport the request is exactly the business, message.send and the keys (77.173892ms)
✔ against a real broker: the message is written once as a human send, and its refusals map (20.236729ms)
✔ loopback host and board origin fail closed (5.246404ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (69.838144ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (42.315831ms)
✔ unreachable board reports URL; CLI rejects unsupported options (332.102951ms)
✔ render prints the unit without the preview flags (9.272347ms)
✔ render carries every serve flag, the preview set included (6.502983ms)
✔ a partial preview set is refused (14.56595ms)
✔ a value the unit cannot carry is refused, for every flag (242.972249ms)
✔ the rendered unit names none of the human proof agent markers and sets no agent variable (17.62552ms)
✔ install into --dir with --no-reload writes the file whole, then reports unchanged (48.821135ms)
✔ install without --no-reload runs daemon-reload and nothing that starts the unit (21.577986ms)
✔ uninstall refuses while the unit is running, with or without --no-reload, and removes it once it is not (110.182469ms)
✔ status prints the unit state and GET /healthz on the installed port (164.447541ms)
✔ usage: an unknown verb or flag, and start or enable, exit 4 (58.464963ms)
✔ without --send, render is the template with its placeholders filled, byte for byte (26.673604ms)
✔ render --send with the preview set ends ExecStart with --send and changes nothing else (41.870801ms)
✔ --send without the whole preview set is refused and writes nothing (110.503764ms)
✔ install --send into --dir with --no-reload writes the sending unit, and status says whether it sends (201.485496ms)
✔ uninstall and status take no --send (28.847597ms)
✔ install refuses while the unit is running, in any state but inactive or failed, and changes nothing (223.121538ms)
✔ when the state read fails, install and uninstall refuse with one line and change nothing (57.604689ms)
✔ install needs --copy naming a copy that exists, and refuses outside the canonical checkout without --allow-worktree (51.365616ms)
✔ install refuses unless the bus unit runs from the same copy, naming both paths, and changes nothing (191.916287ms)
✔ render --copy runs the copy and reads the queue in the checkout; without --copy the unit is unchanged (15.388319ms)
✔ the uid header takes only an Authentik uid; the username header still takes a username (267.548182ms)
✔ a preview value cannot start with - or . (27.603281ms)
✔ status names the copy and whether refactor has moved past it, and says when the unit runs from no copy (358.426375ms)
✔ Settings form shows every scope and held role; gated save, exact payload, retained draft and no automatic retry (1030.434112ms)
2026-10-15T00:59:00.978Z settings user="jason" changed
✔ REQ-WEB-1 signed-in same-origin Settings write reaches exactly one human operation (17.472909ms)
✔ REQ-WEB-1 Settings writes require enabled send, named Host, identity, exact Origin and same-origin Fetch Metadata (7.450798ms)
✔ REQ-WEB-1 Settings validates JSON, size, revision and strict writable keys before broker call (5.021258ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.910911ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.658346ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.964043ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5348.433255ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2332.278823ms)
✔ Queue, Business and Settings in their five states, with no move button (3046.878524ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1162.057997ms)
✔ the server log names no config path when the system config refuses (314.389845ms)
ℹ tests 112
ℹ suites 0
ℹ pass 112
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 133703.305166
@@ -0,0 +1,239 @@
[
{
"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": "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-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-control-board",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/control-board/tests/attention-flow.test.mjs",
"packages/control-board/tests/attention.test.mjs",
"packages/control-board/tests/discord.test.mjs",
"packages/control-board/tests/relaunch.test.mjs",
"packages/control-board/tests/scan.test.mjs",
"packages/control-board/tests/serve.test.mjs"
]
},
{
"suite": "pkg-conversation",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/conversation/tests/claim.test.mjs",
"packages/conversation/tests/cohort.test.mjs",
"packages/conversation/tests/flows.test.mjs",
"packages/conversation/tests/races.test.mjs",
"packages/conversation/tests/reader.test.mjs",
"packages/conversation/tests/smoke.test.mjs",
"packages/conversation/tests/turns.test.mjs"
]
},
{
"suite": "pkg-ledger",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/ledger/tests/gitea-helper-raw.test.mjs",
"packages/ledger/tests/gitea-helper.test.mjs",
"packages/ledger/tests/ledger.test.mjs",
"packages/ledger/tests/queue-checks.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/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": "script-queue",
"exit": 0,
"command": [
"bash",
"scripts/test-queue.sh"
]
},
{
"suite": "script-discord",
"exit": 0,
"command": [
"bash",
"scripts/test-discord.sh"
]
},
{
"suite": "script-task",
"exit": 0,
"command": [
"bash",
"scripts/test-task.sh"
]
},
{
"suite": "pkg-business",
"exit": 0,
"command": [
"node",
"--test",
"--test-concurrency=1",
"--test-reporter=spec",
"packages/business/tests/business.test.mjs",
"packages/business/tests/chief-of-staff.test.mjs",
"packages/business/tests/cli.test.mjs",
"packages/business/tests/credentials.test.mjs",
"packages/business/tests/launch-settings.test.mjs",
"packages/business/tests/project.test.mjs",
"packages/business/tests/resolve.test.mjs",
"packages/business/tests/role.test.mjs",
"packages/business/tests/vars.test.mjs"
]
},
{
"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/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/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-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"
]
}
]
@@ -0,0 +1,82 @@
toolchain: node v26.8.1
OK syntax: packages/discord/src/approvals.mjs
OK syntax: packages/discord/src/authorize.mjs
OK syntax: packages/discord/src/binding.mjs
OK syntax: packages/discord/src/cli.mjs
OK syntax: packages/discord/src/connector.mjs
OK syntax: packages/discord/src/context.mjs
OK syntax: packages/discord/src/engine-pi.mjs
OK syntax: packages/discord/src/errors.mjs
OK syntax: packages/discord/src/gateway.mjs
OK syntax: packages/discord/src/git.mjs
OK syntax: packages/discord/src/journal.mjs
OK syntax: packages/discord/src/notify.mjs
OK syntax: packages/discord/src/rest.mjs
OK syntax: packages/discord/src/setspark.mjs
OK syntax: packages/discord/src/tools.mjs
OK syntax: packages/discord/src/web.mjs
OK syntax: packages/discord/bin/git-credential.mjs
OK syntax: packages/discord/extension/tools.mjs
OK syntax: packages/discord/tests/approvals.test.mjs
OK syntax: packages/discord/tests/authorize.test.mjs
OK syntax: packages/discord/tests/binding.test.mjs
OK syntax: packages/discord/tests/connector.test.mjs
OK syntax: packages/discord/tests/context.test.mjs
OK syntax: packages/discord/tests/copy-service.test.mjs
OK syntax: packages/discord/tests/engine.test.mjs
OK syntax: packages/discord/tests/fake-pi.mjs
OK syntax: packages/discord/tests/gateway.test.mjs
OK syntax: packages/discord/tests/git.test.mjs
OK syntax: packages/discord/tests/helpers.mjs
OK syntax: packages/discord/tests/journal.test.mjs
OK syntax: packages/discord/tests/notify.test.mjs
OK syntax: packages/discord/tests/offline-check.test.mjs
OK syntax: packages/discord/tests/recover.test.mjs
OK syntax: packages/discord/tests/rest.test.mjs
OK syntax: packages/discord/tests/setspark.test.mjs
OK syntax: packages/discord/tests/tools.test.mjs
OK syntax: packages/discord/tests/web.test.mjs
OK syntax: packages/discord/fixtures/claim-worker.mjs
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
OK syntax: scripts/discord.sh
OK syntax: scripts/discord-service.sh
OK packages/discord declares no dependencies
OK no bot-token-shaped string in packages/discord
OK fixture binding uses placeholder ids only
OK fixture binding validates
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
OK real pi with protocol vault adds reserve_id to the git verbs
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
OK real pi refuses a git key on a read-only root (fail closed)
OK real pi with the pilot flags (--no-tools) exposes no tool at all
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test packages/discord/tests/ (ℹ pass 183)
OK scripts/discord.sh --help exits 0
OK scripts/discord.sh check without a binding exits 4
OK scripts/discord.sh recover without a binding exits 4
OK scripts/discord.sh reload without a binding exits 4
OK scripts/discord-service.sh without a command exits 4
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
OK service install writes the rendered unit (0644) and leaves no temp file
OK service install a second time reports unchanged
OK systemd-analyze verify accepts the rendered unit
OK service uninstall removes the unit file
OK service install with an unknown flag exits 4
OK service install with USER unset finishes and names the account for lingering
OK pinned render runs copy CLI with copy cwd/repo and no checkout executable
OK pinned install writes the rendered unit in a temporary unit directory
OK pinned install refuses short prerequisite and writes no unit
OK pinned install refuses missing prerequisite and writes no unit
OK pinned install refuses cli prerequisite and writes no unit
OK pinned install refuses pi prerequisite and writes no unit
OK status names the copy from fake systemctl effective cwd
OK checkout install explicitly recommends --copy
OK wrapper check --offline passes local fixture and makes no network call
OK wrapper run --offline refuses usage
discord suite: 78 passed, 0 failed
@@ -0,0 +1,36 @@
toolchain: node v26.8.1, git version 2.55.0
OK syntax: packages/queue/src/cli.mjs
OK syntax: packages/queue/src/errors.mjs
OK syntax: packages/queue/src/io.mjs
OK syntax: packages/queue/src/lock.mjs
OK syntax: packages/queue/src/queue.mjs
OK syntax: packages/queue/src/review.mjs
OK syntax: packages/queue/src/store.mjs
OK syntax: packages/queue/tests/commit.test.mjs
OK syntax: packages/queue/tests/data.test.mjs
OK syntax: packages/queue/tests/dispatch.test.mjs
OK syntax: packages/queue/tests/gate-owner.test.mjs
OK syntax: packages/queue/tests/helpers.mjs
OK syntax: packages/queue/tests/lock.test.mjs
OK syntax: packages/queue/tests/migration.test.mjs
OK syntax: packages/queue/tests/review.test.mjs
OK syntax: packages/queue/tests/store.test.mjs
OK syntax: packages/queue/tests/write.test.mjs
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
OK syntax: scripts/queue-commit.sh
OK syntax: scripts/git-hooks/pre-commit
OK syntax: scripts/mosaic
OK queue-commit.sh, the guard and scripts/mosaic are executable
OK packages/queue declares no dependencies
ℹ tests 156
ℹ pass 156
ℹ fail 0
OK node --test packages/queue/tests/
OK scripts/mosaic queue help
skip queue verify and render --check: this checkout (/mnt/storage/src/mosaic-stack-rocko-122) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
queue suite: 28 passed, 0 failed
@@ -0,0 +1,101 @@
OK valid task validates (exit 0)
OK unknown task key exits 2 (exit 2)
OK unsupported taskVersion exits 2 (exit 2)
OK invalid task id exits 2 (exit 2)
OK empty prompt exits 2 (exit 2)
OK NUL in expectExact exits 2 (exit 2)
OK out-of-range timeout exits 2 (exit 2)
OK missing mission file exits 4 (exit 4)
OK task with valid mission validates (exit 0)
OK invalid mission exits 2 (exit 2)
OK validate missing task exits 4 (exit 4)
OK validation does not modify the task file
OK prune dry-run exits 0 (exit 0)
OK dry-run deleted nothing
OK prune --keep=2 --yes removes oldest (exit 0)
OK kept exactly 2 newest runs
OK newest run kept, oldest pruned
OK append-only receipt written (3 entries)
OK sessions/workspaces untouched by prune
OK prune with invalid keep exits 4 (exit 4)
OK mock adapter: gate passes on matching mock response (exit 0)
OK mock adapter: expect-mismatch recorded (exit 1)
OK mismatch reason recorded
OK unknown adapter fails closed (exit 1)
OK mission task runs via mock adapter (exit 0)
OK mission section injected into generated prompt
OK retry of mission run succeeds (exit 0)
OK retriedFrom lineage recorded
OK mission section present after retry (relative path resolved)
OK retry of missing run exits 4 (exit 4)
OK skill install bundled ms-tools (exit 0)
OK installed to skills-available
OK double install refuses (exit 1)
OK activate enables skill (exit 0)
OK enabled dir populated
OK uninstall while enabled refuses (exit 1)
OK deactivate moves back to available (exit 0)
OK uninstall removes from available (exit 0)
OK seat with enabled skill launches
OK skill path delivered to adapter
OK shipped researcher contract resolves (ceiling + network)
OK resolve-role refuses a non-role document (conductor policy) (exit 2)
OK resolve-role refuses name/filename mismatch (exit 2)
OK resolve-role refuses unknown network declaration (exit 2)
OK resolve-role refuses duplicate role tool (exit 2)
OK resolve-role refuses unsupported tool (exit 2)
OK resolve-role refuses missing contract file (exit 4)
OK version 1 role prints no contract line
OK shipped version 2 roles resolve with their contracts (pm, cto, coder, reviewer)
OK resolve-role accepts a minimal version 2 role (exit 0)
OK resolve-role refuses an action outside the vocabulary (exit 2)
OK resolve-role refuses a gated-only action in a role file (exit 2)
OK resolve-role refuses a Vikunja verb no role may hold (exit 2)
OK resolve-role refuses a version 2 role whose contract is missing (exit 2)
OK resolve-role refuses a contract that is a symbolic link (exit 2)
OK seat launches under role ceiling
OK role ceiling narrows seat tools (read,write,bash -> read,bash)
OK narrowing recorded loudly
OK missing role contract refuses launch (exit 2)
OK missing-contract refusal names the role
OK empty ceiling intersection -> tool-free seat
OK tool-free outcome recorded loudly
OK overridden agents dir without seat definition refuses (exit 4)
OK seat-resolution refusal names the seat
OK policy: mission only -> mission tools
OK policy: task only -> task tools
OK policy: both -> intersection (task narrowed)
OK policy: empty intersection -> tool-free
OK invalid mission capabilities rejected (exit 2)
mosaic-task: unsupported mission tool: "sudo" (supported: read, write, edit, bash, grep, find, ls)
OK task run with user layer present (exit 0)
OK user context dispatched into generated prompt
OK workspace+tools task runs via mock (exit 0)
OK workspace path + tools delivered to adapter
OK persistent workspace created on host
OK plain task still runs (no workspace/tools) (exit 0)
OK workspace var present but empty when absent
OK tools empty when absent
OK unknown tool exits 2 (exit 2)
OK workspace traversal exits 2 (exit 2)
OK live hello task succeeds with exact marker
OK result.json written in run dir
OK result.json contents are correct
OK wrong expectExact fails with exit 1 (reason: expect-mismatch)
OK each run gets a distinct run dir (no clobber)
OK list shows both runs
OK fork base: teach succeeds (exit 0)
OK fork child recalls ancestor context (exit 0)
OK forked child recalled ancestor code word
OK ancestor session untouched by fork
OK child session dir has its own branch file
OK onboard without name exits 4 (non-interactive) (exit 4)
OK onboard --name renders profile (exit 0)
OK profile written
OK canon structure: required filled, optional placeholdered
OK canon sections present
OK user recall run succeeds
OK recalled user name (response: Jason)
OK no agent identity on headless run
selftest: 98 passed, 0 failed
@@ -0,0 +1,52 @@
[
{
"op": "remove",
"path": "/launch/max"
},
{
"op": "add",
"path": "/launch/models",
"value": {
"opus": {
"provider": "anthropic",
"model": "claude-opus-5-5"
},
"sonnet": {
"provider": "anthropic",
"model": "claude-sonnet-5-5"
}
}
},
{
"op": "add",
"path": "/launch/limits",
"value": {
"models": {
"anthropic": {
"claude-opus-5-5": 4,
"claude-sonnet-5-5": 4
}
}
}
},
{
"op": "replace",
"path": "/roles/pm/vars/model",
"value": "opus"
},
{
"op": "replace",
"path": "/roles/cto/vars/model",
"value": "opus"
},
{
"op": "replace",
"path": "/roles/coder/vars/model",
"value": "sonnet"
},
{
"op": "replace",
"path": "/roles/reviewer/vars/model",
"value": "opus"
}
]
@@ -0,0 +1,43 @@
docs/TOOLS.md: OK
packages/bus/src/broker.mjs: OK
packages/bus/src/runtime.mjs: OK
packages/bus/tests/limits.test.mjs: OK
packages/business/README.md: OK
packages/business/examples/mosaic-stack.example.json: OK
packages/business/src/business.mjs: OK
packages/business/src/index.mjs: OK
packages/business/src/launch-settings.mjs: OK
packages/business/src/launch.mjs: OK
packages/business/src/migrate-launch.mjs: OK
packages/business/src/organization.mjs: OK
packages/business/src/vocabulary.mjs: OK
packages/business/tests/business.test.mjs: OK
packages/business/tests/chief-of-staff.test.mjs: OK
packages/business/tests/helpers.mjs: OK
packages/business/tests/launch-settings.test.mjs: OK
packages/business/tests/resolve.test.mjs: OK
packages/cli/README.md: OK
packages/cli/src/cli.mjs: OK
packages/cli/src/config.mjs: OK
packages/cli/src/launcher.mjs: OK
packages/cli/src/limits.mjs: OK
packages/cli/tests/launch-limits.test.mjs: OK
packages/cli/tests/launcher.test.mjs: OK
packages/cli/tests/limits.test.mjs: OK
packages/harness/src/bundle.mjs: OK
packages/harness/tests/pi-session.test.mjs: OK
packages/harness/tests/runner.test.mjs: OK
packages/seat/src/session.mjs: OK
packages/seat/tests/session.test.mjs: OK
packages/webui/src/bus.mjs: OK
packages/webui/src/cli.mjs: OK
packages/webui/src/public/bus.js: OK
packages/webui/src/reads.mjs: OK
packages/webui/src/serve.mjs: OK
packages/webui/tests/reads-fixture.mjs: OK
packages/webui/tests/reads.test.mjs: OK
packages/webui/tests/settings-form.test.mjs: OK
packages/webui/tests/settings-write.test.mjs: OK
packages/webui/tests/shell.test.mjs: OK
packages/webui/tests/views.test.mjs: OK
scripts/mosaic: OK
@@ -0,0 +1,22 @@
{
"models": {
"opus": {
"provider": "anthropic",
"model": "claude-opus-5-5"
},
"sonnet": {
"provider": "anthropic",
"model": "claude-sonnet-5-5"
}
},
"families": {
"opus": "opus",
"sonnet": "sonnet"
},
"instances": {
"pm": "opus",
"cto": "opus",
"coder": "sonnet",
"reviewer": "opus"
}
}
@@ -0,0 +1,33 @@
01-whole-document-validation KILLED 1
02-atomic-publication KILLED 1
03-previous-version KILLED 1
04-authenticated-actor KILLED 1
05-old-new-receipt KILLED 1
06-stale-revision KILLED 1
07-uncertain-recovery KILLED 1
08-total-limit KILLED 1
09-provider-limit KILLED 1
10-model-limit KILLED 1
11-at-limit KILLED 1
12-unset-unlimited KILLED 1
13-nonnegative-counts KILLED 1
14-no-policy-ceiling KILLED 1
15-exact-provider-count KILLED 1
16-provider-routing KILLED 1
17-reread-every-launch KILLED 1
18-refusal-hit-logged KILLED 1
19-migration-preserve-counts KILLED 1
20-migration-ambiguity KILLED 1
21-print-only-migration KILLED 1
22-human-only-settings KILLED 1
23-settings-origin KILLED 1
24-settings-fetch-metadata KILLED 1
25-settings-send-mode KILLED 1
26-settings-strict-keys KILLED 1
27-form-instances KILLED 1
28-form-limit-payload KILLED 1
29-migration-idempotent KILLED 1
30-zero-stops-new KILLED 1
31-instance-model-edit KILLED 1
32-model-table-edit KILLED 1
33-runtime-writer-wiring KILLED 1
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""One fault per acceptance check. Sequential process groups; whole-group timeout cleanup."""
import json,os,pathlib,signal,subprocess,sys,time
ROOT=pathlib.Path(sys.argv[1] if len(sys.argv)>1 else '/mnt/storage/src/mosaic-stack-rocko-122')
OUT=pathlib.Path(__file__).resolve().parent/'mutants';OUT.mkdir(exist_ok=True)
B='packages/business/tests/launch-settings.test.mjs';C='packages/cli/tests/limits.test.mjs';L='packages/cli/tests/launch-limits.test.mjs';H='packages/bus/tests/limits.test.mjs';W='packages/webui/tests/settings-write.test.mjs';F='packages/webui/tests/settings-form.test.mjs'
cases=[]
def add(id,path,old,new,test):cases.append(dict(id=id,path=path,old=old,new=new,test=test))
p='packages/business/src/organization.mjs'
add('01-whole-document-validation',p,'validateBusinessDocument(document,file,{rolesDir});\n const old','/* missing validation */\n const old',B)
add('02-atomic-publication',p,'renameSync(candidate,file);','/* never publish candidate */',B)
add('03-previous-version',p,'durable(previous,oldBytes);',"durable(previous,'{}');",B)
add('04-authenticated-actor',p,'const event={transaction,actor,business:id',"const event={transaction,actor:'anonymous',business:id",B)
add('05-old-new-receipt',p,'changes:changes(old,document)','changes:[]',B)
add('06-stale-revision',p,'if(revision!==before)', 'if(false)',B)
add('07-uncertain-recovery',p,"if(hash!==e.before&&hash!==e.after)","if(false)",B)
p='packages/business/src/launch.mjs'
add('08-total-limit',p,"['total',limits.total,usage.total]","['total',undefined,usage.total]",B)
add('09-provider-limit',p,'Object.hasOwn(limits.providers??{},provider)?limits.providers[provider]:undefined','undefined',B)
add('10-model-limit',p,'own(own(limits.models,provider),model)','undefined',B)
add('11-at-limit',p,'running>=limit','running>limit',B)
add('12-unset-unlimited',p,"['total',limits.total,usage.total]","['total',limits.total??0,usage.total]",B)
add('13-nonnegative-counts',p,'!Number.isSafeInteger(v)||v<0','!Number.isSafeInteger(v)',B)
add('14-no-policy-ceiling',p,'!Number.isSafeInteger(v)||v<0','!Number.isSafeInteger(v)||v<0||v>4',B)
add('15-exact-provider-count',p,'s.provider!==' , 's.provider!==',B) # replaced below with an effective fault
cases[-1].update(old='pc.set(s.provider,(pc.get(s.provider)??0)+1)',new="pc.set('anthropic',(pc.get('anthropic')??0)+1)")
p='packages/harness/src/bundle.mjs'
add('16-provider-routing',p,'provider:entry.provider',"provider:'anthropic'",B)
p='packages/cli/src/launcher.mjs'
add('17-reread-every-launch',p,'b=loadBusiness(host.business,{dir:configDir(env),rolesDir:rolesDir(env)});','/* cached policy */',L)
add('18-refusal-hit-logged',p,'...(e.limit?{limit:e.limit}:{})','/* no hit details */',L)
p='packages/business/src/migrate-launch.mjs'
add('19-migration-preserve-counts',p,'limits.models[entry.provider][entry.model]=count','limits.models[entry.provider][entry.model]=0',C)
add('20-migration-ambiguity',p,'if(used.has(tuple))','if(false)',B)
p='packages/cli/src/limits.mjs'
add('21-print-only-migration',p,'io.stdout.write(JSON.stringify(migrateLaunch(raw,mapping,file,{rolesDir}),null,2)',"io.stdout.write(JSON.stringify((await import('node:fs')).writeFileSync(file,JSON.stringify(raw))??migrateLaunch(raw,mapping,file,{rolesDir}),null,2)",C)
p='packages/bus/src/broker.mjs'
add('22-human-only-settings',p,"if(!s.human)fail('human-required');if(!this.#organization)","if(false)fail('human-required');if(!this.#organization)",H)
p='packages/webui/src/serve.mjs'
add('23-settings-origin',p,"if(one(req,'origin')!==`https://${preview.host}`)return refuse(403,'cross-origin request refused','settings need the Console origin');","/* origin omitted */",W)
add('24-settings-fetch-metadata',p,"if(one(req,'sec-fetch-site')!=='same-origin')return refuse(403,'cross-site request refused','settings need same-origin Fetch Metadata');","/* fetch metadata omitted */",W)
add('25-settings-send-mode',p,'if(!send||!settingsWrite||!named)', 'if(!settingsWrite||!named)',W)
add('26-settings-strict-keys',p,"Object.keys(value).some(k=>!['revision','limits','models','instances'].includes(k))",'false',W)
p='packages/webui/src/public/bus.js'
add('27-form-instances',p,'Object.entries(v.instances||{})','Object.entries({})',F)
add('28-form-limit-payload',p,"if(kind==='total')limits.total=n;","if(kind==='total')limits.total=1;",F)
add('29-migration-idempotent','packages/business/src/migrate-launch.mjs','if(!out.launch?.max)','if(false)',C)
add('30-zero-stops-new','packages/business/src/launch.mjs','limit!==undefined&&running>=limit','limit!==undefined&&limit>0&&running>=limit',B)
add('31-instance-model-edit','packages/business/src/launch-settings.mjs',"if(Object.hasOwn(args,'instances'))",'if(false)',B)
add('32-model-table-edit','packages/business/src/launch-settings.mjs',"if(Object.hasOwn(args,'models'))next.launch.models=args.models;",'/* ignored model table update */',B)
add('33-runtime-writer-wiring','packages/bus/src/runtime.mjs',"organization:settings?(id,verb,args,actor)=>verb==='limits'?settings.read(id):settings.set(id,args,actor):null,",'organization:null,',H)
# Apply all matching copies for repeated logging sites. Every fault must change source.
results=[]
for c in cases:
file=ROOT/c['path'];before=file.read_text();matches=before.count(c['old'])
if not matches:raise RuntimeError('missing mutation site '+c['id'])
file.write_text(before.replace(c['old'],c['new']))
try:
env={**os.environ,'DOCKER_HOST':'unix:///nonexistent.sock'}
with (OUT/(c['id']+'.log')).open('w') as log:
p=subprocess.Popen(['node','--test',c['test']],cwd=ROOT,env=env,stdout=log,stderr=subprocess.STDOUT,start_new_session=True)
try:rc=p.wait(timeout=120)
except subprocess.TimeoutExpired:os.killpg(p.pid,signal.SIGKILL);p.wait();rc='timeout'
verdict='KILLED' if isinstance(rc,int) and rc!=0 else 'SURVIVED' if rc==0 else 'TIMEOUT'
row={k:c[k] for k in ['id','path','test']};row.update(result=verdict,exit=rc,sites=matches);results.append(row);print(c['id'],verdict,rc,flush=True)
finally:file.write_text(before)
(OUT/'results.json').write_text(json.dumps(results,indent=2)+'\n')
sys.exit(0 if all(r['result']=='KILLED' for r in results) else 1)
@@ -0,0 +1,32 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.290703ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.635748ms)
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (14.181009ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (12.918603ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.045938ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (16.851781ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.824986ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.428471ms)
ℹ tests 8
ℹ suites 0
ℹ pass 7
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 124.073952
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:24:1
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (14.181009ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:29:167)
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: undefined,
expected: undefined,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,69 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.680904ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.699656ms)
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (9.985758ms)
✖ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (14.041343ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.451991ms)
✖ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (9.561879ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.911811ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (7.653004ms)
ℹ tests 8
ℹ suites 0
ℹ pass 5
ℹ fail 3
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 116.612936
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:24:1
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (9.985758ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
undefined !== 99
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:27:9)
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: undefined,
expected: 99,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/business/tests/launch-settings.test.mjs:31:1
✖ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (14.041343ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
undefined !== 7
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:35:244)
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: undefined,
expected: 7,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/business/tests/launch-settings.test.mjs:43:1
✖ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (9.561879ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
undefined !== 0
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:45:118)
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: undefined,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,181 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.426161ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.695339ms)
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (9.823989ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (13.991203ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (2.974418ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (16.183376ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.973498ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.449268ms)
ℹ tests 8
ℹ suites 0
ℹ pass 7
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 121.582613
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:24:1
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (9.823989ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ '{}'
- '{\n' +
- ' "businessVersion": 1,\n' +
- ' "id": "acme",\n' +
- ' "human": "jason",\n' +
- ' "arbiters": {\n' +
- ' "delivery": "pm",\n' +
- ' "technical": "cto"\n' +
- ' },\n' +
- ' "projects": {\n' +
- ' "stack": {\n' +
- ' "root": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/project"\n' +
- ' }\n' +
- ' },\n' +
- ' "vars": {\n' +
- ' "tracker.baseUrl": "http://127.0.0.1:3456",\n' +
- ' "gitea.baseUrl": "https://git.example",\n' +
- ' "tracker.pollSeconds": 60\n' +
- ' },\n' +
- ' "tracker": {\n' +
- ' "sync": {\n' +
- ' "bot": "bot-acme-sync",\n' +
- ' "botId": 9,\n' +
- ' "credentials": {\n' +
- ' "vikunja": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/sync-vikunja.token",\n' +
- ' "expires": "2099-01-01"\n' +
- ' }\n' +
- ' }\n' +
- ' },\n' +
- ' "labels": {}\n' +
- ' },\n' +
- ' "roles": {\n' +
- ' "pm": {\n' +
- ' "definition": "pm",\n' +
- ' "tracker": {\n' +
- ' "bot": "bot-acme-pm",\n' +
- ' "botId": 3\n' +
- ' },\n' +
- ' "credentials": {\n' +
- ' "gitea": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/pm-gitea.token",\n' +
- ' "rotateBy": "2099-01-01"\n' +
- ' },\n' +
- ' "vikunja": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/pm-vikunja.token",\n' +
- ' "expires": "2099-01-01"\n' +
- ' }\n' +
- ' },\n' +
- ' "holder": "sage",\n' +
- ' "vars": {\n' +
- ' "harness": "pi",\n' +
- ' "model": "opus"\n' +
- ' }\n' +
- ' },\n' +
- ' "cto": {\n' +
- ' "definition": "cto",\n' +
- ' "tracker": {\n' +
- ' "bot": "bot-acme-cto",\n' +
- ' "botId": 4\n' +
- ' },\n' +
- ' "credentials": {\n' +
- ' "gitea": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/cto-gitea.token",\n' +
- ' "rotateBy": "2099-01-01"\n' +
- ' },\n' +
- ' "vikunja": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/cto-vikunja.token",\n' +
- ' "expires": "2099-01-01"\n' +
- ' }\n' +
- ' },\n' +
- ' "holder": "darkwing",\n' +
- ' "vars": {\n' +
- ' "harness": "claude-code",\n' +
- ' "model": "sonnet"\n' +
- ' }\n' +
- ' },\n' +
- ' "coder": {\n' +
- ' "definition": "coder",\n' +
- ' "tracker": {\n' +
- ' "bot": "bot-acme-coder",\n' +
- ' "botId": 5\n' +
- ' },\n' +
- ' "credentials": {\n' +
- ' "gitea": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/coder-gitea.token",\n' +
- ' "rotateBy": "2099-01-01"\n' +
- ' },\n' +
- ' "vikunja": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/coder-vikunja.token",\n' +
- ' "expires": "2099-01-01"\n' +
- ' }\n' +
- ' },\n' +
- ' "vars": {\n' +
- ' "harness": "pi",\n' +
- ' "limits.network": "none",\n' +
- ' "model": "sonnet"\n' +
- ' }\n' +
- ' },\n' +
- ' "reviewer": {\n' +
- ' "definition": "reviewer",\n' +
- ' "tracker": {\n' +
- ' "bot": "bot-acme-reviewer",\n' +
- ' "botId": 6\n' +
- ' },\n' +
- ' "credentials": {\n' +
- ' "gitea": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/reviewer-gitea.token",\n' +
- ' "rotateBy": "2099-01-01"\n' +
- ' },\n' +
- ' "vikunja": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/reviewer-vikunja.token",\n' +
- ' "expires": "2099-01-01"\n' +
- ' }\n' +
- ' },\n' +
- ' "vars": {\n' +
- ' "model": "sonnet"\n' +
- ' }\n' +
- ' }\n' +
- ' },\n' +
- ' "launch": {\n' +
- ' "by": "pm",\n' +
- ' "instances": [\n' +
- ' "coder",\n' +
- ' "reviewer"\n' +
- ' ],\n' +
- ' "models": {\n' +
- ' "opus": {\n' +
- ' "provider": "anthropic",\n' +
- ' "model": "claude-opus-5-5"\n' +
- ' },\n' +
- ' "sonnet": {\n' +
- ' "provider": "anthropic",\n' +
- ' "model": "claude-sonnet-5-5"\n' +
- ' }\n' +
- ' },\n' +
- ' "limits": {\n' +
- ' "models": {\n' +
- ' "anthropic": {\n' +
- ' "claude-opus-5-5": 4,\n' +
- ' "claude-sonnet-5-5": 4\n' +
- ' }\n' +
- ' }\n' +
- ' }\n' +
- ' }\n' +
- '}\n'
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:27:131)
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: '{}',
expected: '{\n "businessVersion": 1,\n "id": "acme",\n "human": "jason",\n "arbiters": {\n "delivery": "pm",\n "technical": "cto"\n },\n "projects": {\n "stack": {\n "root": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/project"\n }\n },\n "vars": {\n "tracker.baseUrl": "http://127.0.0.1:3456",\n "gitea.baseUrl": "https://git.example",\n "tracker.pollSeconds": 60\n },\n "tracker": {\n "sync": {\n "bot": "bot-acme-sync",\n "botId": 9,\n "credentials": {\n "vikunja": {\n "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/sync-vikunja.token",\n "expires": "2099-01-01"\n }\n }\n },\n "labels": {}\n },\n "roles": {\n "pm": {\n "definition": "pm",\n "tracker": {\n "bot": "bot-acme-pm",\n "botId": 3\n },\n "credentials": {\n "gitea": {\n "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/pm-gitea.token",\n "rotateBy": "2099-01-01"\n },\n "vikunja": {\n "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/pm-vikunja.token",\n "expires": "2099-01-01"\n }\n },\n "holder": "sage",\n "vars": {\n "harness": "pi",\n "model": "opus"\n }\n },\n "cto": {\n "definition": "cto",\n "tracker": {\n "bot": "bot-acme-cto",\n "botId": 4\n },\n "credentials": {\n "gitea": {\n "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/cto-gitea.token",\n "rotateBy": "2099-01-01"\n },\n "vikunja": {\n "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/cto-vikunja.token",\n "expires": "2099-01-01"\n }\n },\n "holder": "darkwing",\n "vars": {\n "harness": "claude-code",\n "model": "sonnet"\n }\n },\n "coder": {\n "definition": "coder",\n "tracker": {\n "bot": "bot-acme-coder",\n "botId": 5\n },\n "credentials": {\n "gitea": {\n "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/coder-gitea.token",\n "rotateBy": "2099-01-01"\n },\n "vikunja": {\n "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/coder-vikunja.token",\n "expires": "2099-01-01"\n }\n },\n "vars": {\n "harness": "pi",\n "limits.network": "none",\n "model": "sonnet"\n }\n },\n "reviewer": {\n "definition": "reviewer",\n "tracker": {\n "bot": "bot-acme-reviewer",\n "botId": 6\n },\n "credentials": {\n "gitea": {\n "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/reviewer-gitea.token",\n "rotateBy": "2099-01-01"\n },\n "vikunja": {\n "file": "/mnt/storage/scratch/tmp/mosaic-business-QVai8O/secrets/reviewer-vikunja.token",\n "expires": "2099-01-01"\n }\n },\n "vars": {\n "model": "sonnet"\n }\n }\n },\n "launch": {\n "by": "pm",\n "instances": [\n "coder",\n "reviewer"\n ],\n "models": {\n "opus": {\n "provider": "anthropic",\n "model": "claude-opus-5-5"\n },\n "sonnet": {\n "provider": "anthropic",\n "model": "claude-sonnet-5-5"\n }\n },\n "limits": {\n "models": {\n "anthropic": {\n "claude-opus-5-5": 4,\n "claude-sonnet-5-5": 4\n }\n }\n }\n }\n}\n',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,37 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.384409ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.649649ms)
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (9.602478ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (14.18508ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.055677ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (16.582783ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.997196ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.833552ms)
ℹ tests 8
ℹ suites 0
ℹ pass 7
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 120.665278
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:24:1
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (9.602478ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'anonymous'
- 'jason'
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:28:118)
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: 'anonymous',
expected: 'jason',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,35 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.341907ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.74047ms)
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (11.384864ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (14.009624ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (2.839767ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (16.328724ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.755914ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.498586ms)
ℹ tests 8
ℹ suites 0
ℹ pass 7
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 125.442959
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:24:1
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (11.384864ms)
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
assert.ok(e.changes.some(c=>c.path==='launch.limits.total'&&c.new===99))
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:28:182)
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: false,
expected: true,
operator: '==',
diff: 'simple'
}
@@ -0,0 +1,32 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.355789ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.693529ms)
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (13.065261ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (14.036724ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.09433ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (16.810001ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.777943ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (9.190811ms)
ℹ tests 8
ℹ suites 0
ℹ pass 7
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 124.281661
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:24:1
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (13.065261ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:29:49)
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: undefined,
expected: /revision/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,32 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.162371ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.67039ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (15.381795ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (13.864892ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.098891ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (16.985304ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (2.114869ms)
✖ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.304235ms)
ℹ tests 8
ℹ suites 0
ℹ pass 7
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 131.254644
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:53:1
✖ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.304235ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:55:331)
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: undefined,
expected: /match prepared/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,35 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.884664ms)
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (3.432981ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (16.902993ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (16.422889ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.160294ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (17.281125ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (2.176617ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (9.037775ms)
ℹ tests 8
ℹ suites 0
ℹ pass 7
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 149.05054
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:17:1
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (3.432981ms)
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
assert.ok(limitHit(sessions,'acme',limits,{provider:'anthropic',model:'opus-exact'}))
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:19:106)
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: null,
expected: true,
operator: '==',
diff: 'simple'
}
@@ -0,0 +1,42 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (8.992545ms)
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (3.448411ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (15.708566ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (12.643076ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (2.98341ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (15.613014ms)
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.868454ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (7.754013ms)
ℹ tests 8
ℹ suites 0
ℹ pass 6
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 125.468251
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:17:1
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (3.448411ms)
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
assert.ok(limitHit(sessions,'acme',limits,{provider:'anthropic',model:'opus-exact'}))
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:19:106)
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: null,
expected: true,
operator: '==',
diff: 'simple'
}
test at packages/business/tests/launch-settings.test.mjs:47:1
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.868454ms)
TypeError: Cannot read properties of null (reading 'scope')
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:50:357)
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,42 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.311025ms)
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (3.487825ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (17.216306ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (13.249108ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (2.966547ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (15.967335ms)
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.766365ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.121591ms)
ℹ tests 8
ℹ suites 0
ℹ pass 6
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 128.59403
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:17:1
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (3.487825ms)
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
assert.ok(limitHit(sessions,'acme',limits,{provider:'anthropic',model:'opus-exact'}))
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:19:106)
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: null,
expected: true,
operator: '==',
diff: 'simple'
}
test at packages/business/tests/launch-settings.test.mjs:47:1
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.766365ms)
TypeError: Cannot read properties of null (reading 'scope')
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:50:468)
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,42 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.36572ms)
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (3.317877ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (15.706826ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (12.965224ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (2.88238ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (16.714514ms)
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.82258ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.670838ms)
ℹ tests 8
ℹ suites 0
ℹ pass 6
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 131.697313
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:17:1
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (3.317877ms)
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
assert.ok(limitHit(sessions,'acme',limits,{provider:'anthropic',model:'opus-exact'}))
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:19:106)
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: null,
expected: true,
operator: '==',
diff: 'simple'
}
test at packages/business/tests/launch-settings.test.mjs:47:1
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.82258ms)
TypeError: Cannot read properties of null (reading 'scope')
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:50:357)
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,64 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.221271ms)
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (1.277662ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (15.347754ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (13.219557ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.035014ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (19.243366ms)
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (2.077478ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.265283ms)
ℹ tests 8
ℹ suites 0
ℹ pass 6
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 125.845443
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:17:1
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (1.277662ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ {
+ limit: 0,
+ running: 1,
+ scope: 'total'
+ }
- null
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:19:192)
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: { scope: 'total', limit: 0, running: 1 },
expected: null,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/business/tests/launch-settings.test.mjs:47:1
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (2.077478ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ {
+ limit: 0,
+ running: 1,
+ scope: 'total'
+ }
- null
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:50:86)
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: { scope: 'total', limit: 0, running: 1 },
expected: null,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,48 @@
✖ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (7.384622ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.699265ms)
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (15.515455ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (14.861961ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (2.901932ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (15.747433ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.739389ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.511594ms)
ℹ tests 8
ℹ suites 0
ℹ pass 6
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 122.571785
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:10:1
✖ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (7.384622ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:13:104)
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: undefined,
operator: 'throws',
diff: 'simple'
}
test at packages/business/tests/launch-settings.test.mjs:24:1
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (15.515455ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:29:167)
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: undefined,
expected: undefined,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,78 @@
✖ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (6.740972ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.712646ms)
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (5.325904ms)
✖ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (4.817162ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.267214ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (16.930805ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.818123ms)
✖ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (4.314223ms)
ℹ tests 8
ℹ suites 0
ℹ pass 4
ℹ fail 4
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 100.049078
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:10:1
✖ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (6.740972ms)
Error [BusinessError]: /mnt/storage/scratch/tmp/mosaic-business-7xoQNX/businesses/acme.json launch.limits.total must be a whole number of 0 or more
at refuse (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/errors.mjs:12:9)
at count (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/launch.mjs:5:60)
at checkLimits (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/launch.mjs:12:44)
at checkLaunch (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:131:16)
at validateBusinessDocument (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:181:57)
at check (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:11:29)
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:12:257)
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) {
exitCode: 2
}
test at packages/business/tests/launch-settings.test.mjs:24:1
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (5.325904ms)
Error [BusinessError]: /mnt/storage/scratch/tmp/mosaic-business-wST5Ql/businesses/acme.json launch.limits.total must be a whole number of 0 or more
at refuse (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/errors.mjs:12:9)
at count (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/launch.mjs:5:60)
at checkLimits (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/launch.mjs:12:44)
at checkLaunch (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:131:16)
at validateBusinessDocument (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:181:57)
at file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/organization.mjs:30:191
at withLock (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/organization.mjs:21:14)
at Object.write (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/organization.mjs:29:82)
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:26:77)
at async Test.run (node:internal/test_runner/test:1409:7) {
exitCode: 2
}
test at packages/business/tests/launch-settings.test.mjs:31:1
✖ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (4.817162ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:34:9)
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: undefined,
expected: /recovery/,
operator: 'throws',
diff: 'simple'
}
test at packages/business/tests/launch-settings.test.mjs:53:1
✖ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (4.314223ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:55:331)
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: undefined,
expected: /match prepared/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,25 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.591005ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.671667ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (14.861051ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (12.757394ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.083978ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (15.584768ms)
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.877899ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.135097ms)
ℹ tests 8
ℹ suites 0
ℹ pass 7
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 122.400084
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:47:1
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.877899ms)
TypeError: Cannot read properties of null (reading 'scope')
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:50:357)
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,37 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.203608ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.650834ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (14.954179ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (13.327727ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.318633ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (15.906537ms)
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (2.419251ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.06199ms)
ℹ tests 8
ℹ suites 0
ℹ pass 7
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 124.166306
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:47:1
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (2.419251ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'anthropic'
- 'proxy-next'
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:49:153)
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: 'anthropic',
expected: 'proxy-next',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,33 @@
✖ each launch rereads limits and model selections; lowered limits keep sessions and log exact hit (10.935974ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 72.387691
✖ failing tests:
test at packages/cli/tests/launch-limits.test.mjs:10:1
✖ each launch rereads limits and model selections; lowered limits keep sessions and log exact hit (10.935974ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
{
+ error: 'fixture-policy',
- error: 'capacity-full',
ok: false
}
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/cli/tests/launch-limits.test.mjs:16:157)
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: { ok: false, error: 'fixture-policy' },
expected: { ok: false, error: 'capacity-full' },
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,18 @@
✖ each launch rereads limits and model selections; lowered limits keep sessions and log exact hit (10.943146ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 72.374966
✖ failing tests:
test at packages/cli/tests/launch-limits.test.mjs:10:1
✖ each launch rereads limits and model selections; lowered limits keep sessions and log exact hit (10.943146ms)
TypeError: Cannot read properties of undefined (reading 'scope')
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/cli/tests/launch-limits.test.mjs:16:380)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
@@ -0,0 +1,39 @@
✖ migration needs no bus/config, prints whole converted document, is idempotent and never writes (7.139922ms)
✔ limits view/set use the human bus transport and pass only the explicit patch (1.459022ms)
ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 98.476338
✖ failing tests:
test at packages/cli/tests/limits.test.mjs:11:1
✖ migration needs no bus/config, prints whole converted document, is idempotent and never writes (7.139922ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
{
models: {
anthropic: {
+ 'claude-opus-5-5': 0,
+ 'claude-sonnet-5-5': 0
- 'claude-opus-5-5': 4,
- 'claude-sonnet-5-5': 4
}
}
}
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/cli/tests/limits.test.mjs:17:60)
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: { models: { anthropic: [Object] } },
expected: { models: { anthropic: [Object] } },
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,32 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.096879ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.668043ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (15.959188ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (14.288691ms)
✖ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (5.975697ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (18.6972ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (2.126869ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (9.520592ms)
ℹ tests 8
ℹ suites 0
ℹ pass 7
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 134.249462
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:38:1
✖ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (5.975697ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:41:729)
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: undefined,
expected: /ambiguous/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,161 @@
✖ migration needs no bus/config, prints whole converted document, is idempotent and never writes (9.520892ms)
✔ limits view/set use the human bus transport and pass only the explicit patch (1.606131ms)
ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 110.51898
✖ failing tests:
test at packages/cli/tests/limits.test.mjs:11:1
✖ migration needs no bus/config, prints whole converted document, is idempotent and never writes (9.520892ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ '{"businessVersion":1,"id":"acme","human":"jason","arbiters":{"delivery":"pm","technical":"cto"},"projects":{"stack":{"root":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/project"}},"vars":{"tracker.baseUrl":"http://127.0.0.1:3456","gitea.baseUrl":"https://git.example","tracker.pollSeconds":60},"tracker":{"sync":{"bot":"bot-acme-sync","botId":9,"credentials":{"vikunja":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/sync-vikunja.token","expires":"2099-01-01"}}},"labels":{}},"roles":{"pm":{"definition":"pm","tracker":{"bot":"bot-acme-pm","botId":3},"credentials":{"gitea":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/pm-gitea.token","rotateBy":"2099-01-01"},"vikunja":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/pm-vikunja.token","expires":"2099-01-01"}},"holder":"sage","vars":{"harness":"pi","model":"claude-opus-5-5"}},"cto":{"definition":"cto","tracker":{"bot":"bot-acme-cto","botId":4},"credentials":{"gitea":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/cto-gitea.token","rotateBy":"2099-01-01"},"vikunja":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/cto-vikunja.token","expires":"2099-01-01"}},"holder":"darkwing","vars":{"harness":"claude-code","model":"claude-opus-5-5"}},"coder":{"definition":"coder","tracker":{"bot":"bot-acme-coder","botId":5},"credentials":{"gitea":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/coder-gitea.token","rotateBy":"2099-01-01"},"vikunja":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/coder-vikunja.token","expires":"2099-01-01"}},"vars":{"harness":"pi","limits.network":"none","model":"claude-sonnet-5-5"}},"reviewer":{"definition":"reviewer","tracker":{"bot":"bot-acme-reviewer","botId":6},"credentials":{"gitea":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/reviewer-gitea.token","rotateBy":"2099-01-01"},"vikunja":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/reviewer-vikunja.token","expires":"2099-01-01"}},"vars":{"model":"claude-opus-5-5"}}},"launch":{"by":"pm","instances":["coder","reviewer"],"max":{"opus":4,"sonnet":4}}}'
- '{\n' +
- ' "businessVersion": 1,\n' +
- ' "id": "acme",\n' +
- ' "human": "jason",\n' +
- ' "arbiters": {\n' +
- ' "delivery": "pm",\n' +
- ' "technical": "cto"\n' +
- ' },\n' +
- ' "projects": {\n' +
- ' "stack": {\n' +
- ' "root": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/project"\n' +
- ' }\n' +
- ' },\n' +
- ' "vars": {\n' +
- ' "tracker.baseUrl": "http://127.0.0.1:3456",\n' +
- ' "gitea.baseUrl": "https://git.example",\n' +
- ' "tracker.pollSeconds": 60\n' +
- ' },\n' +
- ' "tracker": {\n' +
- ' "sync": {\n' +
- ' "bot": "bot-acme-sync",\n' +
- ' "botId": 9,\n' +
- ' "credentials": {\n' +
- ' "vikunja": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/sync-vikunja.token",\n' +
- ' "expires": "2099-01-01"\n' +
- ' }\n' +
- ' }\n' +
- ' },\n' +
- ' "labels": {}\n' +
- ' },\n' +
- ' "roles": {\n' +
- ' "pm": {\n' +
- ' "definition": "pm",\n' +
- ' "tracker": {\n' +
- ' "bot": "bot-acme-pm",\n' +
- ' "botId": 3\n' +
- ' },\n' +
- ' "credentials": {\n' +
- ' "gitea": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/pm-gitea.token",\n' +
- ' "rotateBy": "2099-01-01"\n' +
- ' },\n' +
- ' "vikunja": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/pm-vikunja.token",\n' +
- ' "expires": "2099-01-01"\n' +
- ' }\n' +
- ' },\n' +
- ' "holder": "sage",\n' +
- ' "vars": {\n' +
- ' "harness": "pi",\n' +
- ' "model": "claude-opus-5-5"\n' +
- ' }\n' +
- ' },\n' +
- ' "cto": {\n' +
- ' "definition": "cto",\n' +
- ' "tracker": {\n' +
- ' "bot": "bot-acme-cto",\n' +
- ' "botId": 4\n' +
- ' },\n' +
- ' "credentials": {\n' +
- ' "gitea": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/cto-gitea.token",\n' +
- ' "rotateBy": "2099-01-01"\n' +
- ' },\n' +
- ' "vikunja": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/cto-vikunja.token",\n' +
- ' "expires": "2099-01-01"\n' +
- ' }\n' +
- ' },\n' +
- ' "holder": "darkwing",\n' +
- ' "vars": {\n' +
- ' "harness": "claude-code",\n' +
- ' "model": "claude-opus-5-5"\n' +
- ' }\n' +
- ' },\n' +
- ' "coder": {\n' +
- ' "definition": "coder",\n' +
- ' "tracker": {\n' +
- ' "bot": "bot-acme-coder",\n' +
- ' "botId": 5\n' +
- ' },\n' +
- ' "credentials": {\n' +
- ' "gitea": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/coder-gitea.token",\n' +
- ' "rotateBy": "2099-01-01"\n' +
- ' },\n' +
- ' "vikunja": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/coder-vikunja.token",\n' +
- ' "expires": "2099-01-01"\n' +
- ' }\n' +
- ' },\n' +
- ' "vars": {\n' +
- ' "harness": "pi",\n' +
- ' "limits.network": "none",\n' +
- ' "model": "claude-sonnet-5-5"\n' +
- ' }\n' +
- ' },\n' +
- ' "reviewer": {\n' +
- ' "definition": "reviewer",\n' +
- ' "tracker": {\n' +
- ' "bot": "bot-acme-reviewer",\n' +
- ' "botId": 6\n' +
- ' },\n' +
- ' "credentials": {\n' +
- ' "gitea": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/reviewer-gitea.token",\n' +
- ' "rotateBy": "2099-01-01"\n' +
- ' },\n' +
- ' "vikunja": {\n' +
- ' "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/reviewer-vikunja.token",\n' +
- ' "expires": "2099-01-01"\n' +
- ' }\n' +
- ' },\n' +
- ' "vars": {\n' +
- ' "model": "claude-opus-5-5"\n' +
- ' }\n' +
- ' }\n' +
- ' },\n' +
- ' "launch": {\n' +
- ' "by": "pm",\n' +
- ' "instances": [\n' +
- ' "coder",\n' +
- ' "reviewer"\n' +
- ' ],\n' +
- ' "max": {\n' +
- ' "opus": 4,\n' +
- ' "sonnet": 4\n' +
- ' }\n' +
- ' }\n' +
- '}\n'
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/cli/tests/limits.test.mjs:17:216)
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: '{"businessVersion":1,"id":"acme","human":"jason","arbiters":{"delivery":"pm","technical":"cto"},"projects":{"stack":{"root":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/project"}},"vars":{"tracker.baseUrl":"http://127.0.0.1:3456","gitea.baseUrl":"https://git.example","tracker.pollSeconds":60},"tracker":{"sync":{"bot":"bot-acme-sync","botId":9,"credentials":{"vikunja":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/sync-vikunja.token","expires":"2099-01-01"}}},"labels":{}},"roles":{"pm":{"definition":"pm","tracker":{"bot":"bot-acme-pm","botId":3},"credentials":{"gitea":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/pm-gitea.token","rotateBy":"2099-01-01"},"vikunja":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/pm-vikunja.token","expires":"2099-01-01"}},"holder":"sage","vars":{"harness":"pi","model":"claude-opus-5-5"}},"cto":{"definition":"cto","tracker":{"bot":"bot-acme-cto","botId":4},"credentials":{"gitea":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/cto-gitea.token","rotateBy":"2099-01-01"},"vikunja":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/cto-vikunja.token","expires":"2099-01-01"}},"holder":"darkwing","vars":{"harness":"claude-code","model":"claude-opus-5-5"}},"coder":{"definition":"coder","tracker":{"bot":"bot-acme-coder","botId":5},"credentials":{"gitea":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/coder-gitea.token","rotateBy":"2099-01-01"},"vikunja":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/coder-vikunja.token","expires":"2099-01-01"}},"vars":{"harness":"pi","limits.network":"none","model":"claude-sonnet-5-5"}},"reviewer":{"definition":"reviewer","tracker":{"bot":"bot-acme-reviewer","botId":6},"credentials":{"gitea":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/reviewer-gitea.token","rotateBy":"2099-01-01"},"vikunja":{"file":"/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/reviewer-vikunja.token","expires":"2099-01-01"}},"vars":{"model":"claude-opus-5-5"}}},"launch":{"by":"pm","instances":["coder","reviewer"],"max":{"opus":4,"sonnet":4}}}',
expected: '{\n "businessVersion": 1,\n "id": "acme",\n "human": "jason",\n "arbiters": {\n "delivery": "pm",\n "technical": "cto"\n },\n "projects": {\n "stack": {\n "root": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/project"\n }\n },\n "vars": {\n "tracker.baseUrl": "http://127.0.0.1:3456",\n "gitea.baseUrl": "https://git.example",\n "tracker.pollSeconds": 60\n },\n "tracker": {\n "sync": {\n "bot": "bot-acme-sync",\n "botId": 9,\n "credentials": {\n "vikunja": {\n "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/sync-vikunja.token",\n "expires": "2099-01-01"\n }\n }\n },\n "labels": {}\n },\n "roles": {\n "pm": {\n "definition": "pm",\n "tracker": {\n "bot": "bot-acme-pm",\n "botId": 3\n },\n "credentials": {\n "gitea": {\n "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/pm-gitea.token",\n "rotateBy": "2099-01-01"\n },\n "vikunja": {\n "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/pm-vikunja.token",\n "expires": "2099-01-01"\n }\n },\n "holder": "sage",\n "vars": {\n "harness": "pi",\n "model": "claude-opus-5-5"\n }\n },\n "cto": {\n "definition": "cto",\n "tracker": {\n "bot": "bot-acme-cto",\n "botId": 4\n },\n "credentials": {\n "gitea": {\n "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/cto-gitea.token",\n "rotateBy": "2099-01-01"\n },\n "vikunja": {\n "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/cto-vikunja.token",\n "expires": "2099-01-01"\n }\n },\n "holder": "darkwing",\n "vars": {\n "harness": "claude-code",\n "model": "claude-opus-5-5"\n }\n },\n "coder": {\n "definition": "coder",\n "tracker": {\n "bot": "bot-acme-coder",\n "botId": 5\n },\n "credentials": {\n "gitea": {\n "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/coder-gitea.token",\n "rotateBy": "2099-01-01"\n },\n "vikunja": {\n "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/coder-vikunja.token",\n "expires": "2099-01-01"\n }\n },\n "vars": {\n "harness": "pi",\n "limits.network": "none",\n "model": "claude-sonnet-5-5"\n }\n },\n "reviewer": {\n "definition": "reviewer",\n "tracker": {\n "bot": "bot-acme-reviewer",\n "botId": 6\n },\n "credentials": {\n "gitea": {\n "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/reviewer-gitea.token",\n "rotateBy": "2099-01-01"\n },\n "vikunja": {\n "file": "/mnt/storage/scratch/tmp/mosaic-cli-8UsXUg/secrets/reviewer-vikunja.token",\n "expires": "2099-01-01"\n }\n },\n "vars": {\n "model": "claude-opus-5-5"\n }\n }\n },\n "launch": {\n "by": "pm",\n "instances": [\n "coder",\n "reviewer"\n ],\n "max": {\n "opus": 4,\n "sonnet": 4\n }\n }\n}\n',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,28 @@
✖ limits binds actor from human proof and rejects role, reader, tester and forged actor (10.289673ms)
✔ runtime wires verified human settings to the actual shared file writer and actor receipt (30.832045ms)
ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 90.307525
✖ failing tests:
test at packages/bus/tests/limits.test.mjs:9:1
✖ limits binds actor from human proof and rejects role, reader, tester and forged actor (10.289673ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/bus/tests/limits.test.mjs:13:89)
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: /human-required/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,33 @@
2026-10-15T01:03:58.803Z settings user="jason" changed
✔ REQ-WEB-1 signed-in same-origin Settings write reaches exactly one human operation (17.035029ms)
2026-10-15T01:03:58.813Z settings user="jason" changed
✖ REQ-WEB-1 Settings writes require enabled send, named Host, identity, exact Origin and same-origin Fetch Metadata (7.685464ms)
✔ REQ-WEB-1 Settings validates JSON, size, revision and strict writable keys before broker call (7.21557ms)
ℹ tests 3
ℹ suites 0
ℹ pass 2
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 126.801032
✖ failing tests:
test at packages/webui/tests/settings-write.test.mjs:12:1
✖ REQ-WEB-1 Settings writes require enabled send, named Host, identity, exact Origin and same-origin Fetch Metadata (7.685464ms)
AssertionError [ERR_ASSERTION]: origin
200 !== 403
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/webui/tests/settings-write.test.mjs:14:361)
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: 200,
expected: 403,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,33 @@
2026-10-15T01:03:58.968Z settings user="jason" changed
✔ REQ-WEB-1 signed-in same-origin Settings write reaches exactly one human operation (17.478842ms)
2026-10-15T01:03:58.977Z settings user="jason" changed
✖ REQ-WEB-1 Settings writes require enabled send, named Host, identity, exact Origin and same-origin Fetch Metadata (6.461883ms)
✔ REQ-WEB-1 Settings validates JSON, size, revision and strict writable keys before broker call (6.073723ms)
ℹ tests 3
ℹ suites 0
ℹ pass 2
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 121.943876
✖ failing tests:
test at packages/webui/tests/settings-write.test.mjs:12:1
✖ REQ-WEB-1 Settings writes require enabled send, named Host, identity, exact Origin and same-origin Fetch Metadata (6.461883ms)
AssertionError [ERR_ASSERTION]: sec-fetch-site
200 !== 403
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/webui/tests/settings-write.test.mjs:14:361)
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: 200,
expected: 403,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,33 @@
2026-10-15T01:03:59.126Z settings user="jason" changed
✔ REQ-WEB-1 signed-in same-origin Settings write reaches exactly one human operation (15.007364ms)
2026-10-15T01:03:59.131Z settings user="jason" changed
✖ REQ-WEB-1 Settings writes require enabled send, named Host, identity, exact Origin and same-origin Fetch Metadata (3.015053ms)
✔ REQ-WEB-1 Settings validates JSON, size, revision and strict writable keys before broker call (6.258677ms)
ℹ tests 3
ℹ suites 0
ℹ pass 2
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 113.43808
✖ failing tests:
test at packages/webui/tests/settings-write.test.mjs:12:1
✖ REQ-WEB-1 Settings writes require enabled send, named Host, identity, exact Origin and same-origin Fetch Metadata (3.015053ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
200 !== 403
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/webui/tests/settings-write.test.mjs:13:40)
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: true,
code: 'ERR_ASSERTION',
actual: 200,
expected: 403,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,33 @@
2026-10-15T01:03:59.297Z settings user="jason" changed
✔ REQ-WEB-1 signed-in same-origin Settings write reaches exactly one human operation (16.974911ms)
✔ REQ-WEB-1 Settings writes require enabled send, named Host, identity, exact Origin and same-origin Fetch Metadata (7.155105ms)
2026-10-15T01:03:59.309Z settings user="jason" changed
✖ REQ-WEB-1 Settings validates JSON, size, revision and strict writable keys before broker call (2.556465ms)
ℹ tests 3
ℹ suites 0
ℹ pass 2
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 120.011625
✖ failing tests:
test at packages/webui/tests/settings-write.test.mjs:16:1
✖ REQ-WEB-1 Settings validates JSON, size, revision and strict writable keys before broker call (2.556465ms)
AssertionError [ERR_ASSERTION]: [object Object]
200 !== 400
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/webui/tests/settings-write.test.mjs:17:124)
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: 200,
expected: 400,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,29 @@
✖ Settings form shows every scope and held role; gated save, exact payload, retained draft and no automatic retry (485.108511ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 580.318499
✖ failing tests:
test at packages/webui/tests/settings-form.test.mjs:8:1
✖ Settings form shows every scope and held role; gated save, exact payload, retained draft and no automatic retry (485.108511ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 4
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/webui/tests/settings-form.test.mjs:15:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
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: 4,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,29 @@
✖ Settings form shows every scope and held role; gated save, exact payload, retained draft and no automatic retry (675.168813ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 781.59553
✖ failing tests:
test at packages/webui/tests/settings-form.test.mjs:8:1
✖ Settings form shows every scope and held role; gated save, exact payload, retained draft and no automatic retry (675.168813ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
1 !== 0
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/webui/tests/settings-form.test.mjs:19:279)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
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: 1,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,23 @@
✖ migration needs no bus/config, prints whole converted document, is idempotent and never writes (8.135325ms)
✔ limits view/set use the human bus transport and pass only the explicit patch (1.228645ms)
ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 102.242229
✖ failing tests:
test at packages/cli/tests/limits.test.mjs:11:1
✖ migration needs no bus/config, prints whole converted document, is idempotent and never writes (8.135325ms)
Error [CliError]: old launch.max must be a JSON object
at limitsCommand (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/cli/src/limits.mjs:20:51)
at main (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/cli/src/cli.mjs:384:33)
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/cli/tests/limits.test.mjs:19:175)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
exitCode: 2
}
@@ -0,0 +1,42 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.278537ms)
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (3.467213ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (17.028977ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (13.265697ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (2.916609ms)
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (15.596687ms)
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.883872ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.325958ms)
ℹ tests 8
ℹ suites 0
ℹ pass 6
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 127.56547
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:17:1
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (3.467213ms)
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
assert.ok(limitHit(sessions,'acme',JSON.parse(JSON.stringify(limits).replaceAll(':1',':0')),{provider:'anthropic',model:'opus-exact'}))
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:19:336)
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: null,
expected: true,
operator: '==',
diff: 'simple'
}
test at packages/business/tests/launch-settings.test.mjs:47:1
✖ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.883872ms)
TypeError: Cannot read properties of null (reading 'running')
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:51:104)
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,35 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.145634ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.638626ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (17.96131ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (13.305164ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.095582ms)
✖ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (9.995205ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.786146ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.182564ms)
ℹ tests 8
ℹ suites 0
ℹ pass 7
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 123.244628
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:43:1
✖ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (9.995205ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'sonnet' !== 'opus'
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:45:154)
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: 'sonnet',
expected: 'opus',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,37 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (9.394723ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.710015ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (16.148881ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (13.225129ms)
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (3.199033ms)
✖ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (15.869312ms)
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (1.829359ms)
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (8.674257ms)
ℹ tests 8
ℹ suites 0
ℹ pass 7
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 125.721441
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:43:1
✖ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (15.869312ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'anthropic'
- 'proxy'
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:45:357)
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: 'anthropic',
expected: 'proxy',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,24 @@
✔ limits binds actor from human proof and rejects role, reader, tester and forged actor (15.12507ms)
✖ runtime wires verified human settings to the actual shared file writer and actor receipt (20.360184ms)
ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 87.995927
✖ failing tests:
test at packages/bus/tests/limits.test.mjs:17:1
✖ runtime wires verified human settings to the actual shared file writer and actor receipt (20.360184ms)
Error: not-configured
at fail (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/bus/src/broker.mjs:76:9)
at Broker.request (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/bus/src/broker.mjs:672:67)
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/bus/tests/limits.test.mjs:22:24)
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) {
code: 'not-configured'
}
@@ -0,0 +1,266 @@
[
{
"id": "01-whole-document-validation",
"path": "packages/business/src/organization.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "02-atomic-publication",
"path": "packages/business/src/organization.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "03-previous-version",
"path": "packages/business/src/organization.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "04-authenticated-actor",
"path": "packages/business/src/organization.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "05-old-new-receipt",
"path": "packages/business/src/organization.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "06-stale-revision",
"path": "packages/business/src/organization.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "07-uncertain-recovery",
"path": "packages/business/src/organization.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "08-total-limit",
"path": "packages/business/src/launch.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "09-provider-limit",
"path": "packages/business/src/launch.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "10-model-limit",
"path": "packages/business/src/launch.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "11-at-limit",
"path": "packages/business/src/launch.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "12-unset-unlimited",
"path": "packages/business/src/launch.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "13-nonnegative-counts",
"path": "packages/business/src/launch.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "14-no-policy-ceiling",
"path": "packages/business/src/launch.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "15-exact-provider-count",
"path": "packages/business/src/launch.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "16-provider-routing",
"path": "packages/harness/src/bundle.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "17-reread-every-launch",
"path": "packages/cli/src/launcher.mjs",
"test": "packages/cli/tests/launch-limits.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "18-refusal-hit-logged",
"path": "packages/cli/src/launcher.mjs",
"test": "packages/cli/tests/launch-limits.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 3
},
{
"id": "19-migration-preserve-counts",
"path": "packages/business/src/migrate-launch.mjs",
"test": "packages/cli/tests/limits.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "20-migration-ambiguity",
"path": "packages/business/src/migrate-launch.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "21-print-only-migration",
"path": "packages/cli/src/limits.mjs",
"test": "packages/cli/tests/limits.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "22-human-only-settings",
"path": "packages/bus/src/broker.mjs",
"test": "packages/bus/tests/limits.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "23-settings-origin",
"path": "packages/webui/src/serve.mjs",
"test": "packages/webui/tests/settings-write.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "24-settings-fetch-metadata",
"path": "packages/webui/src/serve.mjs",
"test": "packages/webui/tests/settings-write.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "25-settings-send-mode",
"path": "packages/webui/src/serve.mjs",
"test": "packages/webui/tests/settings-write.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "26-settings-strict-keys",
"path": "packages/webui/src/serve.mjs",
"test": "packages/webui/tests/settings-write.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "27-form-instances",
"path": "packages/webui/src/public/bus.js",
"test": "packages/webui/tests/settings-form.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "28-form-limit-payload",
"path": "packages/webui/src/public/bus.js",
"test": "packages/webui/tests/settings-form.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "29-migration-idempotent",
"path": "packages/business/src/migrate-launch.mjs",
"test": "packages/cli/tests/limits.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "30-zero-stops-new",
"path": "packages/business/src/launch.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "31-instance-model-edit",
"path": "packages/business/src/launch-settings.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "32-model-table-edit",
"path": "packages/business/src/launch-settings.mjs",
"test": "packages/business/tests/launch-settings.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
},
{
"id": "33-runtime-writer-wiring",
"path": "packages/bus/src/runtime.mjs",
"test": "packages/bus/tests/limits.test.mjs",
"result": "KILLED",
"exit": 1,
"sites": 1
}
]
@@ -0,0 +1,5 @@
{
"network": "mosaic-stack-rocko-122_default",
"present": true,
"removeExit": 0
}
@@ -0,0 +1,41 @@
✔ config directory and file path follow MOSAIC_CONFIG (1.096295ms)
✔ the fixture business validates and comes back frozen (3.476817ms)
✔ two instances may share a definition (1.106543ms)
✔ top-level refusals (3.164339ms)
✔ arbiters and projects (4.729453ms)
✔ role instances (2.15204ms)
✔ Vikunja bots (5.100335ms)
✔ a role without Vikunja takes no tracker block (1.899838ms)
✔ credential references match the definition's services (2.139965ms)
✔ launch (6.298037ms)
✔ loadBusiness: file checks (1.513672ms)
✔ loadBusiness: not a regular file (39.322942ms)
✔ loading writes nothing (1.211783ms)
✔ names that are Object.prototype properties don't count as declared (2.819482ms)
✖ the shipped example refuses as written and validates once filled in (0.670781ms)
ℹ tests 15
ℹ suites 0
ℹ pass 14
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 131.337894
✖ failing tests:
test at packages/business/tests/business.test.mjs:206:1
✖ the shipped example refuses as written and validates once filled in (0.670781ms)
Error [BusinessError]: /mnt/storage/scratch/tmp/mosaic-business-AmTlus/businesses/mosaic-stack.json launch.max needs migration: mosaic limits migrate --file FILE --models FILE
at refuse (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/errors.mjs:12:9)
at checkLaunch (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:117:36)
at validateBusinessDocument (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:181:57)
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/business.test.mjs:216:20)
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) {
exitCode: 2
}
@@ -0,0 +1,12 @@
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (14.859099ms)
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.740574ms)
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (14.238045ms)
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (18.865075ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 132.870063
@@ -0,0 +1,81 @@
✖ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (5.393935ms)
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.584145ms)
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (0.386899ms)
✖ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (0.327761ms)
ℹ tests 4
ℹ suites 0
ℹ pass 0
ℹ fail 4
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 96.06669
✖ failing tests:
test at packages/business/tests/launch-settings.test.mjs:10:1
✖ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (5.393935ms)
Error [BusinessError]: unsupported /mnt/storage/scratch/tmp/mosaic-business-UQTbUT/businesses/acme.json launch key: "models"
at refuse (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/errors.mjs:12:9)
at rejectUnknownKeys (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/util.mjs:17:33)
at checkLaunch (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:117:3)
at validateBusinessDocument (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/business.mjs:186:57)
at check (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:11:29)
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs:12:257)
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) {
exitCode: 2
}
test at packages/business/tests/launch-settings.test.mjs:17:1
✖ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.584145ms)
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/launch.mjs' imported from /mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs
at finalizeResolution (node:internal/modules/esm/resolve:272:11)
at moduleResolve (node:internal/modules/esm/resolve:879:10)
at defaultResolve (node:internal/modules/esm/resolve:1006:11)
at #cachedDefaultResolve (node:internal/modules/esm/loader:708:20)
at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:728:38)
at ModuleLoader.resolveSync (node:internal/modules/esm/loader:766:56)
at #resolve (node:internal/modules/esm/loader:690:17)
at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:610:35)
at node:internal/modules/esm/loader:639:32
at TracingChannel.tracePromise (node:diagnostics_channel:547:22) {
code: 'ERR_MODULE_NOT_FOUND',
url: 'file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/launch.mjs'
}
test at packages/business/tests/launch-settings.test.mjs:23:1
✖ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (0.386899ms)
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/organization.mjs' imported from /mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs
at finalizeResolution (node:internal/modules/esm/resolve:272:11)
at moduleResolve (node:internal/modules/esm/resolve:879:10)
at defaultResolve (node:internal/modules/esm/resolve:1006:11)
at #cachedDefaultResolve (node:internal/modules/esm/loader:708:20)
at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:728:38)
at ModuleLoader.resolveSync (node:internal/modules/esm/loader:766:56)
at #resolve (node:internal/modules/esm/loader:690:17)
at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:610:35)
at node:internal/modules/esm/loader:639:32
at TracingChannel.tracePromise (node:diagnostics_channel:547:22) {
code: 'ERR_MODULE_NOT_FOUND',
url: 'file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/organization.mjs'
}
test at packages/business/tests/launch-settings.test.mjs:30:1
✖ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (0.327761ms)
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/organization.mjs' imported from /mnt/storage/src/mosaic-stack-rocko-122/packages/business/tests/launch-settings.test.mjs
at finalizeResolution (node:internal/modules/esm/resolve:272:11)
at moduleResolve (node:internal/modules/esm/resolve:879:10)
at defaultResolve (node:internal/modules/esm/resolve:1006:11)
at #cachedDefaultResolve (node:internal/modules/esm/loader:708:20)
at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:728:38)
at ModuleLoader.resolveSync (node:internal/modules/esm/loader:766:56)
at #resolve (node:internal/modules/esm/loader:690:17)
at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:610:35)
at node:internal/modules/esm/loader:639:32
at TracingChannel.tracePromise (node:diagnostics_channel:547:22) {
code: 'ERR_MODULE_NOT_FOUND',
url: 'file:///mnt/storage/src/mosaic-stack-rocko-122/packages/business/src/organization.mjs'
}
@@ -0,0 +1,49 @@
✖ REQ-WEB-1 signed-in same-origin Settings write reaches exactly one human operation (15.228773ms)
✔ REQ-WEB-1 Settings writes require enabled send, named Host, identity, exact Origin and same-origin Fetch Metadata (7.031981ms)
✖ REQ-WEB-1 Settings validates JSON, size, revision and strict writable keys before broker call (2.026028ms)
ℹ tests 3
ℹ suites 0
ℹ pass 1
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 114.550473
✖ failing tests:
test at packages/webui/tests/settings-write.test.mjs:11:1
✖ REQ-WEB-1 signed-in same-origin Settings write reaches exactly one human operation (15.228773ms)
AssertionError [ERR_ASSERTION]: {"error":"read-only","message":"the Console preview is read-only"}
403 !== 200
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/webui/tests/settings-write.test.mjs:11:153)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 403,
expected: 200,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/webui/tests/settings-write.test.mjs:16:1
✖ REQ-WEB-1 Settings validates JSON, size, revision and strict writable keys before broker call (2.026028ms)
AssertionError [ERR_ASSERTION]: [object Object]
403 !== 400
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-122/packages/webui/tests/settings-write.test.mjs:17:124)
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: 403,
expected: 400,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,74 @@
c7a236c2734261e332f51e0850c179f401d9a156d91c20279ad967af85025f60 agents/rocko/work/queue-122/r1/BUILD.md
2603c2e0a6a56c305a3418032b5b0298d203f9f13da4823e71a6bc9c5f5fd002 agents/rocko/work/queue-122/r1/MIGRATION.md
1c7bd1ed91530d042579db8b34b9e37c82f5f40a4db0efe74fe9f043a9e49f94 agents/rocko/work/queue-122/r1/MUTANTS.md
3287b9dba2ac897922a412741cd307f8b78ceb5376d9a2da3fd380cecf8db28b agents/rocko/work/queue-122/r1/base-drift.json
46bad0bea556403fab5df8949314a931fae804c8b3d095047fd3d9a5f58ffc66 agents/rocko/work/queue-122/r1/base.txt
26edcd30e09e60d0ce44293577ab6da8a90479148168e002b8131cc3472fb276 agents/rocko/work/queue-122/r1/build.patch
667441df2411db4c085ec6981d8c9c5f55578debc02a389a2f4df9283df775ae agents/rocko/work/queue-122/r1/candidate-manifest.sha256
5e349186a0356f0892e8e349b2c310d796419d07778352d287497bcb3a522f8e agents/rocko/work/queue-122/r1/gate/pkg-bus.log
e566227494fe9ea1645424dd75067450f4435f3d7cfcc219d209e8a67f76cecd agents/rocko/work/queue-122/r1/gate/pkg-business.log
735c675ee50f3ce293bee42dfd136b4cb062b7e4e4c980417b672b453472de09 agents/rocko/work/queue-122/r1/gate/pkg-cli.log
0dbc6d3fe9e5220c47312f58f5ffd07fb84bd437c273e72ca36b205c626c83cb agents/rocko/work/queue-122/r1/gate/pkg-control-board.log
b48553197fff19ecc891d92bb585b31e39f0c4621659d3e5547b4bb6b80c874a agents/rocko/work/queue-122/r1/gate/pkg-conversation.log
10f8515d3ac53576306b960b04c1724b81c60aa94dae2c3364503312b4f277d5 agents/rocko/work/queue-122/r1/gate/pkg-harness-before-fixture-update.log
9483ccf7cedd55575800de326d375144e037320f1059b23e8947262a69e4a926 agents/rocko/work/queue-122/r1/gate/pkg-harness.log
7d2602fe6c400456afbbcf4ec742ac5d0886c807040e1a89588f2ed0211f12e0 agents/rocko/work/queue-122/r1/gate/pkg-ledger.log
ac7aec341cd452d8a867031c4db59fd77937bb4ffe6ed26f60f2f873faaf3432 agents/rocko/work/queue-122/r1/gate/pkg-seat.log
d08a035739d5d2b5811c9547d5ef724054aaab1a8aa8515e8310328b09c59dba agents/rocko/work/queue-122/r1/gate/pkg-tasks.log
702a1bb22c2d86ff604b4fa72be2cb3a7d81a915d26b89c02ded734bc7f1af02 agents/rocko/work/queue-122/r1/gate/pkg-webui-before-boundary-update.log
4495f5400a503737aa429f539781f9c32358062a9e346a0726618928ee1eeed8 agents/rocko/work/queue-122/r1/gate/pkg-webui.log
38d8ff697b2a7dbe883fe2f6e4d60ab1508ad10692e873cf6b7060a52b1da2a8 agents/rocko/work/queue-122/r1/gate/results.json
87d2e24ce0be7586e31ea1f2c9ce5618b3127515b3c3cd3893f2a0ed899d5822 agents/rocko/work/queue-122/r1/gate/script-discord.log
82f49634c4d027fb10d8810f8cdeaac678e44cca7f57c827ca3534eeb82ee0c7 agents/rocko/work/queue-122/r1/gate/script-queue.log
ccea37f4ffde1f160dcd1a1864988083a645d974e892065c956b338d0f8c853a agents/rocko/work/queue-122/r1/gate/script-task.log
e43c5310975f427894818343ef3f5a4f4eed5d4408a45150920f01bf8798dfa8 agents/rocko/work/queue-122/r1/gate-followup.log
4c8b5d1f6d7efeb7056e3cc181060d67969f2450828e51a301d88c156739ac0a agents/rocko/work/queue-122/r1/gate-run-final.log
1847ca64119a13f1b0483e0f8e1043ef808ec3faf92e9c311bc0951e453fd8a3 agents/rocko/work/queue-122/r1/gate-run.log
04c7ea2380342f9c348285fb518dba420a19a1f611a36ff621f12187add2d774 agents/rocko/work/queue-122/r1/gate.py
31ee5b38cc165f33957b08b15fcccfdcf685f66a6007b97a8e6dae0742f000e6 agents/rocko/work/queue-122/r1/jason-migration.patch.json
3ed49e2ac8439020b27c2c7495ea0c60c19c4a7424701cbe56b5ca77586a0160 agents/rocko/work/queue-122/r1/manifest-check.txt
4f82fd79d4d10d973a1aaed8fb4a3fd58eaa67f9a364c86c60029eaea11a1d23 agents/rocko/work/queue-122/r1/migration-map.json
915c248891939bd4185331b3197358c2b0dfc38b8915497a7ac95ae04776f252 agents/rocko/work/queue-122/r1/mutants/01-whole-document-validation.log
5ed4877a6f503bea39f2669b271b2214e1a0ae0cd4fd9d5df741646aa519e202 agents/rocko/work/queue-122/r1/mutants/02-atomic-publication.log
cf84453811491e78356f904653abd9341c86381c32a8432a5007b4a3f0997a71 agents/rocko/work/queue-122/r1/mutants/03-previous-version.log
20afffba98ba69cfc5e8b6468883636d903de9c46d20e5ae7b34981dfc4c5157 agents/rocko/work/queue-122/r1/mutants/04-authenticated-actor.log
ada69cff34e1991679bb12aebf4796bc13d4c350742c568c087fc1244e1ff612 agents/rocko/work/queue-122/r1/mutants/05-old-new-receipt.log
0ad442c14f505d04423b109edefd5f6d81e5cc0c9f0e56365a17a6ff40b24b40 agents/rocko/work/queue-122/r1/mutants/06-stale-revision.log
24c8d3c5bf846cdfeea8abe159ad04da39c102b1659d633647a7f1fad8183725 agents/rocko/work/queue-122/r1/mutants/07-uncertain-recovery.log
38afcf200c63f6958ce637ea8675d58e86ca8089b16def96465020b94ddb54dc agents/rocko/work/queue-122/r1/mutants/08-total-limit.log
776ddc795d903c915d28b05a69c0f317fc80ce0f10cbe1a97f63936bea8ba314 agents/rocko/work/queue-122/r1/mutants/09-provider-limit.log
5302cd97a145d9fb0cbe99a64d2bea236c7c7578bd56f0cd74e664761cbb3950 agents/rocko/work/queue-122/r1/mutants/10-model-limit.log
e91f29b5a255503dda9480c6c00cf8139e12d0e93903f06bd7e6aeab14614895 agents/rocko/work/queue-122/r1/mutants/11-at-limit.log
f213a1541bf1694ea3070906b8cb53dc1b90d3ff2aa08fb88022a44bfa749daa agents/rocko/work/queue-122/r1/mutants/12-unset-unlimited.log
a4abb65b43827ae987a4908b72f0a796b57d0edd1f5cdefd112f2759b9e45065 agents/rocko/work/queue-122/r1/mutants/13-nonnegative-counts.log
5c145126061ae32b0e15106b5cfd83c5d10c356498a72235a5aca22c92b34a24 agents/rocko/work/queue-122/r1/mutants/14-no-policy-ceiling.log
ea331eef2d8add73199d6e7b727134fed51fbb10b2e0955b51d71f8701173230 agents/rocko/work/queue-122/r1/mutants/15-exact-provider-count.log
46425b451f542bd02ceeb4e77b189892957ad73ef582ac62684083f7ef2439b1 agents/rocko/work/queue-122/r1/mutants/16-provider-routing.log
48f6b305ef1bb8621e699e037ec0d34485698a20ce1a1c4dce70324f9bf4a653 agents/rocko/work/queue-122/r1/mutants/17-reread-every-launch.log
1aba11ec1610a01f36a346a9b16af2df1f4b99fc3ecdffc7e159f2e1ba806302 agents/rocko/work/queue-122/r1/mutants/18-refusal-hit-logged.log
336bea88d6e32ea527941c82124a7aad0ec14e6c80f765c8be57f106ca852afe agents/rocko/work/queue-122/r1/mutants/19-migration-preserve-counts.log
020ff9fc8248abca9c722a892b3308ebdacb1375ab0186966690d5451090b203 agents/rocko/work/queue-122/r1/mutants/20-migration-ambiguity.log
37224d0a22f6e19b607149aa82fb66c87a09f54c622cfbeccf188b170a13fb20 agents/rocko/work/queue-122/r1/mutants/21-print-only-migration.log
51d37658221ff699a35434b9d2e21cf8ae7c27fba5a5eb58e5b66db4eefade6d agents/rocko/work/queue-122/r1/mutants/22-human-only-settings.log
7b705b36dc158b72de50063a7d2b742089f5044c9aa4f7f76cf3690063bac157 agents/rocko/work/queue-122/r1/mutants/23-settings-origin.log
c20d931c5496c9ed3e535cd10d0d1d00570d309c649e1faeef7da4ec66602e0f agents/rocko/work/queue-122/r1/mutants/24-settings-fetch-metadata.log
4a1ad0006668c77cc6eb1bc2bc388b3b60ab38ce894b2399b86c73ea9a9f3ce6 agents/rocko/work/queue-122/r1/mutants/25-settings-send-mode.log
fd1db6f7901762dbb5ded8c1241b288f5b2be34a6ffd1ed0f08b60b44f540a50 agents/rocko/work/queue-122/r1/mutants/26-settings-strict-keys.log
c08ea093c50d95e1282cc1592b8e5d00d0feed74d8cf1d434f8a0aa8dab0cd22 agents/rocko/work/queue-122/r1/mutants/27-form-instances.log
2062afc08db15bacd0728f0fe08237053199dce47ff2fe516c90a1954efd1212 agents/rocko/work/queue-122/r1/mutants/28-form-limit-payload.log
c6bb573f3c8dc21dd68b97bafeff531ee5428ec9bef7d94d75da708ae974e4f4 agents/rocko/work/queue-122/r1/mutants/29-migration-idempotent.log
d3991dc610029dd2c74f448bb4638445b40494ddaf31995a47884915a58e81e3 agents/rocko/work/queue-122/r1/mutants/30-zero-stops-new.log
85b23ad9cb83dc55ae3ef66f2f60039203cae8810982b63a51375c6494f10499 agents/rocko/work/queue-122/r1/mutants/31-instance-model-edit.log
69a011e8a2237f9242c3470a44aa14018076360519de1a7e474d8f2a2d1ef65a agents/rocko/work/queue-122/r1/mutants/32-model-table-edit.log
4f472113774ece5109ca061807e87faf6584f468f930cae6e6d45447e6ec6ee3 agents/rocko/work/queue-122/r1/mutants/33-runtime-writer-wiring.log
2a71db9c81a7cfc5bca19906d1c6f42356c2af231db4e4c41eac8fe0928d211f agents/rocko/work/queue-122/r1/mutants/results.json
b2bbc0e9ee66790b767b6d08793acb43be7d02f34f3285ddf46d88fb2f457a9b agents/rocko/work/queue-122/r1/mutants-run.log
69e301cde4278e0c2e9a21db6ce75b6a69caae634ac6c661147bbd168be907c9 agents/rocko/work/queue-122/r1/mutants.py
2c3eaff1c2f2bcb500f945836754565af52290a4bd579907b42186212bc7c95f agents/rocko/work/queue-122/r1/network-cleanup.json
9142f61b031c002426c68c78b7f6cf81aaae3ba9de7894b993660976e2b61a19 agents/rocko/work/queue-122/r1/out/business-compat.txt
c52dfa99bf1b5f6b12078c60dd07a09af66c767f4c240b9db015e1aaf8bc33ab agents/rocko/work/queue-122/r1/out/green-business-first.txt
3df05fa0a8086c043c0a48a090030d0a779dd2e9c36227a90fd34e037a53aad3 agents/rocko/work/queue-122/r1/out/red-business.txt
22674326f5f3231df9dea449b606783928ba726ab88c7cf1fd7b90b6d78915e0 agents/rocko/work/queue-122/r1/out/red-settings-route.txt
2c370725fcb24d3faf6d7bee1efe7fb76967dc25cd62ba07b7fd377d8a9328a2 agents/rocko/work/queue-122/r1/shell-readers.txt
a32bd57c705e63ae1f55596a67bc8a62431addb75f347dc0f1995bad4bce0318 agents/rocko/work/queue-122/r1/sweep.log
970dcdfe0fb05e487b9696eae36b385e015867cf18fa1e06f0ab6b763484a7cb agents/rocko/work/queue-122/r1/sweep.py
@@ -0,0 +1,43 @@
2:# Suite for packages/queue (issue #1508). Every test runs in scratch
11:# `scripts/mosaic queue`. Each takes the queue lock for a moment and writes
14:# `scripts/mosaic queue help` still runs, reading no queue file and taking no
36:for f in packages/queue/src/*.mjs packages/queue/tests/*.mjs packages/queue/tests/fixtures/*.mjs packages/queue/tests/fixtures/*.sh scripts/queue-commit.sh scripts/git-hooks/pre-commit scripts/mosaic; do
38: *.sh|scripts/mosaic) bash -n "$f" >/dev/null 2>&1 ;;
44:[ -x scripts/queue-commit.sh ] && [ -x scripts/git-hooks/pre-commit ] && [ -x scripts/mosaic ]
45:check "queue-commit.sh, the guard and scripts/mosaic are executable" $?
46:node -e 'const p=require("./packages/queue/package.json"); process.exit(p.dependencies||p.devDependencies?1:0)' >/dev/null 2>&1
47:check "packages/queue declares no dependencies" $?
50:env -u NODE_TEST_CONTEXT node --test --test-reporter=spec packages/queue/tests/ > "$SANDBOX/tests.log" 2>&1
54:check "node --test packages/queue/tests/" "$rc"
57:scripts/mosaic queue help > "$SANDBOX/help.out" 2>&1 && grep -q '^usage: queue list' "$SANDBOX/help.out"
58:check "scripts/mosaic queue help" $?
71: scripts/mosaic queue verify
72: check "scripts/mosaic queue verify (live queue)" $?
73: scripts/mosaic queue render --check
74: check "scripts/mosaic queue render --check (live queue)" $?
scripts/mosaic-task.mjs:37:import { BusinessError, validateRoleDocument } from "../packages/business/src/index.mjs";
scripts/mosaic-task.mjs:629: if (!target) fail(4, "usage: mosaic-task.mjs validate <taskFile>");
scripts/mosaic-task.mjs:637: if (!target) fail(4, "usage: mosaic-task.mjs run <taskFile>");
scripts/mosaic-task.mjs:641: if (!target) fail(4, "usage: mosaic-task.mjs show <runId>");
scripts/mosaic-task.mjs:651: if (!target) fail(4, "usage: mosaic-task.mjs resolve-role <roleFile>");
scripts/mosaic-task.mjs:659: if (!target) fail(4, "usage: mosaic-task.mjs retry <runId>");
scripts/test-task.sh:53:TASK="node scripts/mosaic-task.mjs"
scripts/test-task.sh:119:expect_exit "prune dry-run exits 0" 0 -- env MOSAIC_CONFIG="$SANDBOX/prune-config.json" node scripts/mosaic-task.mjs prune
scripts/test-task.sh:123:expect_exit "prune --keep=2 --yes removes oldest" 0 -- env MOSAIC_CONFIG="$SANDBOX/prune-config.json" node scripts/mosaic-task.mjs prune --keep=2 --yes
scripts/test-task.sh:136:expect_exit "prune with invalid keep exits 4" 4 -- env MOSAIC_CONFIG="$SANDBOX/prune-config.json" node scripts/mosaic-task.mjs prune --keep=0 --yes
scripts/test-task.sh:182: node scripts/mosaic-task.mjs retry "$(ls -dt "$SANDBOX/data/runs"/r-* | head -1 | xargs basename)"
scripts/test-task.sh:192: env MOSAIC_CONFIG="$SANDBOX/mock-adapters.json" node scripts/mosaic-task.mjs retry r-missing
scripts/test-task.sh:243: ROLE_OUT="$(node scripts/mosaic-task.mjs resolve-role roles/researcher.json 2>/dev/null)" \
scripts/test-task.sh:248: node scripts/mosaic-task.mjs resolve-role roles/conductor-policy.json
scripts/test-task.sh:252: node scripts/mosaic-task.mjs resolve-role "$SANDBOX/roles/mismatch.json"
scripts/test-task.sh:255: node scripts/mosaic-task.mjs resolve-role "$SANDBOX/roles/badnet.json"
scripts/test-task.sh:258: node scripts/mosaic-task.mjs resolve-role "$SANDBOX/roles/dupe.json"
scripts/test-task.sh:261: node scripts/mosaic-task.mjs resolve-role "$SANDBOX/roles/aliens.json"
scripts/test-task.sh:263: node scripts/mosaic-task.mjs resolve-role "$SANDBOX/roles/absent.json"
scripts/test-task.sh:269: V2_OUT="$(node scripts/mosaic-task.mjs resolve-role "roles/$r.json" 2>/dev/null)" \
scripts/test-task.sh:281: node scripts/mosaic-task.mjs resolve-role "$SANDBOX/roles/v2ok.json"
scripts/test-task.sh:284: node scripts/mosaic-task.mjs resolve-role "$SANDBOX/roles/v2unknown.json"
scripts/test-task.sh:287: node scripts/mosaic-task.mjs resolve-role "$SANDBOX/roles/v2gated.json"
scripts/test-task.sh:290: node scripts/mosaic-task.mjs resolve-role "$SANDBOX/roles/v2scope.json"
scripts/test-task.sh:293: node scripts/mosaic-task.mjs resolve-role "$SANDBOX/roles/v2nocontract.json"
scripts/test-task.sh:297: node scripts/mosaic-task.mjs resolve-role "$SANDBOX/roles/v2link.json"
+2
View File
@@ -0,0 +1,2 @@
{"before": []}
{"after": []}
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env python3
"""Sweep only Rocko122 node/chromium processes. Never match another seat/live copy."""
import os,pathlib,signal,time,json,sys
root='/mnt/storage/src/mosaic-stack-rocko-122'
def owned():
found=[]
for p in pathlib.Path('/proc').glob('[0-9]*'):
try:
argv=(p/'cmdline').read_bytes().split(b'\0');exe=pathlib.Path(argv[0].decode()).name;cwd=os.readlink(p/'cwd')
if exe not in ['node','chromium','chrome','chrome_crashpad_handler']:continue
if cwd==root or cwd.startswith(root+'/') or any(root.encode() in a for a in argv):found.append({'pid':int(p.name),'exe':exe,'cwd':cwd})
except (OSError,UnicodeError):continue
return found
before=owned();print(json.dumps({'before':before}))
for row in before:
try:os.kill(row['pid'],signal.SIGTERM)
except ProcessLookupError:pass
if before:time.sleep(1)
for row in owned():
try:os.kill(row['pid'],signal.SIGKILL)
except ProcessLookupError:pass
if before:time.sleep(.2)
after=owned();print(json.dumps({'after':after}));sys.exit(bool(after))