work: Rocko row 108 round 1 packet (#1602), candidate manifest 19ef7133db59

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-14 15:12:31 -05:00
co-authored by Claude Opus 5.5
parent d743048362
commit a6e4fe747a
60 changed files with 2544 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
# Row 108 — Bus unit drop-in guard, usage and wrapper (#1602)
Rocko, GPT-6.1-Sol. Reviewer Filbert. Base 234121b30 (base.txt).
Sage assignment sage-0223 and slot grant sage-0224, 2026-10-14.
Detached worktree /mnt/storage/src/mosaic-stack-rocko-108. No candidate
code commit or live action; Sage moves in-review and requests as jarvis.
## Scope and implementation
Same existing guard scans each systemd-analyze lookup directory for the
bus instance/template drop-ins plus mosaic-.service.d and service.d.
Overriding ExecStart/WorkingDirectory, unreadable directory/file or discovery
failure refuses before writing. Tests pin both inherited directory types,
both overriding fields, unchanged prior unit, allowed Environment-only
files, and unreadable dangling directory/file symlinks. All systemd reads
and writes in tests hit fakes, install --dir temporary --no-reload only.
Header render/install comments now include --copy, --copies and --pm.
Dead show replies were removed from both host install fakes and all stack
install fakes. The systemd261 template-show rejection remains deliberately
exercised by a fixture-only probe; the status fake's show arm is reached by
status. Thus every retained show arm has a test and no successful template
show is reintroduced.
Wrapper writes copy-root and Q81 mosaic-old lease comments after the marker.
Marker stays line 2, exact lease stays unchanged, copy exec stays last.
Tests assert both comments, lease execution, same-copy check success and a
foreign marker refusal with all effective roots and the exec path matching.
No wrapper preservation beyond these defined lines or copy-path change.
## Install-script audit — recorded finding, no policy change
Read-only audit-install-scripts.py reads package-lock and installed package
metadata (versions verified); out/install-scripts-audit.json records hashes,
lifecycle commands, copied-source mentions and external bundle imports.
No npm lifecycle is executed by the audit, and it reads no config/token.
| Package | Pinned version / blocked lifecycle | Runtime relevance |
| --- | --- | --- |
| @google/genai | 1.52.0; preinstall echoes preinstall: no-op | No copied source reference or external bundled Pi import; script creates no runtime artifact. |
| esbuild | 0.28.1; postinstall node install.js installs/checks the build binary | No copied source reference or external bundled Pi import; copy runs published code and does not build Pi. |
| protobufjs | 7.6.6; postinstall node scripts/postinstall | No copied source reference or external bundled Pi import; inspected script reads package versions and warns about dependency version schemes, creates no runtime artifact. |
The dependency edge is not sufficient to declare an installed entrypoint
active: unbundled pi-ai providers do import @google/genai when selected,
but Mosaic's paths launch the published **bundled** CLI, not those SDK
modules. Controller imports PI_BIN from conversation/src/pi-pin.mjs:34,
which selects node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js;
adapters/pi/adapter.sh execs pi, whose package.json bin selects the same
entry. The audit scans every bundle chunk, including dynamic literal
imports and require, and finds no external load of these three entrypoints.
Installed unbundled SDK/extension paths or future runtime loaders would need
a new audit; this finding does not authorize allowScripts. README states
why the present copy warnings are expected. No blocked script is needed by
the observed current copy paths; no allowScripts change is proposed.
## Verification and honest correction
Focused red: four expected assertion failures against unchanged product
code (out/red.txt). Focused green: four pass after the minimal changes.
The first full gate passed; first mutants killed 6/7. The foreign marker
mutant survived because the foreign fixture also had a mismatched exec.
The final fixture matches its exec and all effective roots, isolating the
marker; its normal test passes, and the full mutation/gate runs are repeated.
First receipts remain out/full-first and out/mutants-first.
Mutants use their own process groups and kill the group on timeout, restore
source in finally and run serially. DOCKER_HOST=unix:///nonexistent.sock for
all node/script/mutant runs. Docker-dependent script checks are explicitly
skipped, not passing claims. CLI includes the real pinned npm-ci integration
fixture; no live bus, Console, binding or token is read. Final counts,
mutation table and process/network-clear receipts follow below.
## Protected boundary and review
No real systemd read/write, install/enable/reload/restart or ~/.npm-global/bin
access. No source commit, push or live re-pin. This row takes effect only
in a future copy/re-pin separately approved in Mos's window. Row114 starts
only after this row's landing, on a base including it. Decision137: any
review doc-only follow-ups ship in a separate packet after landing.
## Final gate and mutation evidence
| Suite | Passed | Failures / coverage |
| --- | ---: | --- |
| CLI node | 187 | 0 failed, skipped or cancelled |
| test-auth.sh | 15 | 0 |
| test-conductor.sh | 17 | 0 |
| test-config.sh | 24 | 0 |
| test-discord.sh | 66 | 0; also Discord node 178 |
| test-extension-package.sh | 18 | 0 |
| test-foundation.sh | 44 | 0; also foundation node 80 |
| test-land-work.sh | 84 | 0 |
| test-queue.sh | 28 | 0; also queue node 156; two canonical-root checks skipped |
| test-release.sh | 4 | 0; Docker state-machine cases skipped |
| test-task.sh | 26 | 0; four Docker-dependent blocks skipped |
DOCKER_HOST=unix:///nonexistent.sock. Task skips the adapter/mission/retry/
skills/roles/seat/policy/user-injection block, workspace/capability block,
live task/session-fork block and live recall pair. It does not establish
Docker/provider coverage. No observed #258/#261/#262/#263 gate recurrence.
| Mutant | Exit | Result |
| --- | ---: | --- |
| miss-prefix | 1 | killed |
| miss-all-services | 1 | killed |
| ignore-unreadable-directory | 1 | killed |
| ignore-unreadable-file | 1 | killed |
| omit-copy-comment | 1 | killed |
| omit-lease-comment | 1 | killed |
| accept-foreign-check-wrapper | 1 | killed |
Final 7/7 killed, no timeout. Final full gate 20:08:16-20:09:52 UTC.
Own process-group timeout cleanup is in mutants.py. Runner completions
were confirmed before process-sweep.py exited 0 with busy []. Docker's
read-only network inspect explicitly reported mosaic-stack-rocko-108_default
not found, so no network removal was needed. Slot returned to Sage at
20:09:59 UTC. No subsequent tests.
Candidate manifest SHA256:
19ef7133db592787c24fa473357687a5741ccd7823627b970624c930a7611dbc.
6/6 candidate hashes match after all fault injections. build.patch applies
to canonical HEAD and diff --check is clean; no candidate-path drift.
No source commit or protected action performed. Sage owns the in-review
move/request; row remains in-progress at builder rev1178 until that move.
Own append-only log commit: d7430483626f82c8c8c39b84cae03925d2c811bb. Candidate source remains uncommitted.
@@ -0,0 +1,25 @@
"""Read-only audit of pinned local source and installed package metadata. No import/evaluation."""
from pathlib import Path
import json,re,hashlib
root=Path.cwd();lock=json.loads((root/'package-lock.json').read_text());report={}
names=['@google/genai','esbuild','protobufjs'];bundle=root/'node_modules/@earendil-works/pi-coding-agent/dist/bundle'
for name in names:
p=root/'node_modules'/name/'package.json';pkg=json.loads(p.read_text());entry=lock['packages']['node_modules/'+name];assert pkg['version']==entry['version']
report[name]={'version':entry['version'],'hasInstallScript':entry.get('hasInstallScript'),'lifecycle':{k:v for k,v in pkg['scripts'].items() if k in ['preinstall','postinstall']},'installedPackageJsonSha256':hashlib.sha256(p.read_bytes()).hexdigest(),'externalBundleImports':[]}
for p in bundle.rglob('*.js'):
s=p.read_text()
for m in re.finditer(r'(?:from\s*|\bimport\s*\(|\brequire\s*\(|\b__require\s*\()\s*["\']([^"\']+)',s):
for name in names:
if m.group(1)==name or m.group(1).startswith(name+'/'):report[name]['externalBundleImports'].append({'file':str(p.relative_to(root)),'specifier':m.group(1)})
# Direct package specifiers in the files the copy archives. No node_modules
# SDK entrypoint is assumed active merely because npm installed it.
script=(root/'scripts/stack-copy.sh').read_text();block=script.split('COPY_PATHS=(\n',1)[1].split('\n)',1)[0];paths=block.split()
for name in names:report[name]['copiedSourceMentions']=[]
for rel in paths:
p=root/rel;files=p.rglob('*') if p.is_dir() else [p]
for f in files:
if f.is_file() and f.suffix in ['.mjs','.js','.sh']:
s=f.read_text()
for name in names:
if name in s:report[name]['copiedSourceMentions'].append(str(f.relative_to(root)))
print(json.dumps(report,indent=2))
+1
View File
@@ -0,0 +1 @@
234121b3028143b7b0e0a9bb1a7d45d53bfe30b3
+205
View File
@@ -0,0 +1,205 @@
diff --git a/docs/TOOLS.md b/docs/TOOLS.md
index 8fecefb40..20be532e9 100644
--- a/docs/TOOLS.md
+++ b/docs/TOOLS.md
@@ -291,7 +291,8 @@ Failure publishes nothing and removes the temporary tree.
`wrapper` is a live switching step, reserved for Jason or Mos. It accepts
only an existing copy and a regular non-symlink mosaic wrapper with the
Q81 marker on line 2 and its exact lease line to mosaic-old. It preserves
-both lines and atomically replaces the wrapper's final exec with the
+both lines, adds comments naming the copy root and Q81's mosaic-old lease
+probe, and atomically replaces the wrapper's final exec with the
copy's scripts/mosaic. --bin is a fixture override; the default is
~/.npm-global/bin. Neither the Q81 worktree nor mosaic-old is removed.
`check` changes nothing: it reads effective systemd WorkingDirectory and
@@ -305,7 +306,8 @@ Bus render/install accept --copy ID, --copies DIR, --pm and the unchanged
--pm adds the PM startup; a refusal exits 3, and RestartPreventExitStatus
prevents a retry. Install refuses any busy bus instance, even with
--no-reload; a failed state/drop-in read also refuses. Any template or
-instance drop-in assigning ExecStart or WorkingDirectory refuses,
+instance drop-in, or inherited mosaic-.service.d/ or service.d/ drop-in,
+assigning ExecStart or WorkingDirectory refuses,
naming the file. Status reads effective roots, --pm and DropInPaths,
reports checkout or copy identity and refactor drift, and writes nothing.
diff --git a/packages/cli/README.md b/packages/cli/README.md
index 4c05b4213..fb4532c84 100644
--- a/packages/cli/README.md
+++ b/packages/cli/README.md
@@ -470,11 +470,25 @@ mosaic-stack/copies/<full id>. bus-service.sh render/install take --copy
ID and --pm alongside the existing --claude pin. Without --copy render
keeps today's checkout unit. A PM refusal exits 3 and the unit does not
retry it. Install refuses every busy instance (also --no-reload), failed
-state reads and drop-ins overriding ExecStart or WorkingDirectory.
+state reads and drop-ins overriding ExecStart or WorkingDirectory, including
+inherited mosaic-.service.d/ and service.d/ in every unit lookup directory.
Status reads effective systemd properties, reports the tree/copy, PM flag,
refactor drift and drop-in files; it changes nothing. The temporary PM
ExecStart drop-in must be removed by Mos before installing the pinned unit.
+The wrapper keeps the Q81 marker on line 2 and the final copy exec on the
+last line. Comments name the copy root and explain that the Q81 lease probe
+still answers at mosaic-old.
+
+Copy npm ci may warn that @google/genai's preinstall, esbuild's postinstall
+and protobufjs's postinstall are blocked. This is expected for the current
+copy paths: Mosaic launches Pi's published bundled CLI, whose runtime does
+not load those installed package entrypoints. @google/genai's preinstall
+only echoes; esbuild's script installs/checks its build binary; protobufjs's
+script only warns about dependency version schemes. The copy runs the
+already published bundle and does not build it. No allowScripts change is
+needed for these paths; adding a runtime loader requires a fresh audit.
+
The one update and rollback plan is [TOOLS.md, Pinned stack update](../../docs/TOOLS.md#pinned-stack-update).
Each live switch/unit change/stop/start requires Mos's go; agents build
and test fixtures only. Restarting the bus ends its sessions, so no PM
diff --git a/packages/cli/tests/host.test.mjs b/packages/cli/tests/host.test.mjs
index 6a1170357..63dab06c3 100644
--- a/packages/cli/tests/host.test.mjs
+++ b/packages/cli/tests/host.test.mjs
@@ -828,7 +828,7 @@ test("bus-service.sh renders the unit and installs it into a given directory", (
// A user unit cannot order on a system target (Darkwing F3).
assert.doesNotMatch(render.stdout, /network-online/);
const fakeBin=join(dir,"bin");mkdirSync(fakeBin);
- writeFileSync(join(fakeBin,"systemctl"),"#!/bin/sh\ncase \"$*\" in *show*) echo DropInPaths=;; esac\nexit 0\n");chmodSync(join(fakeBin,"systemctl"),0o755);
+ writeFileSync(join(fakeBin,"systemctl"),"#!/bin/sh\ncase \"$*\" in *list-units*) exit 0;; *)exit 99;; esac\n");chmodSync(join(fakeBin,"systemctl"),0o755);
writeFileSync(join(fakeBin,"systemd-analyze"),`#!/bin/sh\nprintf '%s\\n' '${dir}/unit-search'\n`);chmodSync(join(fakeBin,"systemd-analyze"),0o755);
const unitEnv={...process.env,PATH:`${fakeBin}:${process.env.PATH}`};
const first = spawnSync(script, ["install", "--dir", dir, "--no-reload"], { encoding: "utf8",env:unitEnv });
@@ -852,7 +852,7 @@ test("bus-service.sh pins the claude binary, refuses one the unit can't carry, a
return p;
};
mkdirSync(join(dir, "bin"));
- writeFileSync(join(dir,"bin","systemctl"),'#!/bin/sh\ncase "$*" in *show*) echo DropInPaths=;; esac\nexit 0\n');chmodSync(join(dir,"bin","systemctl"),0o755);
+ writeFileSync(join(dir,"bin","systemctl"),'#!/bin/sh\ncase "$*" in *list-units*) exit 0;; *)exit 99;; esac\n');chmodSync(join(dir,"bin","systemctl"),0o755);
writeFileSync(join(dir,"bin","systemd-analyze"),`#!/bin/sh\nprintf '%s\\n' '${dir}/unit-search'\n`);chmodSync(join(dir,"bin","systemd-analyze"),0o755);
const onPath = stand(join(dir, "bin", "claude"));
const named = stand(join(dir, "claude-named"));
diff --git a/packages/cli/tests/stack-copy.test.mjs b/packages/cli/tests/stack-copy.test.mjs
index 8460b9198..d98e771ac 100644
--- a/packages/cli/tests/stack-copy.test.mjs
+++ b/packages/cli/tests/stack-copy.test.mjs
@@ -46,6 +46,11 @@ test('wrapper preserves Q81 lease and marker, rejects foreign/symlink; check use
const lease=`[ "\${1:-}" = __lease-capability ] && exec "${s.bin}/mosaic-old" "$@"`;
writeFileSync(wrapper,`#!/usr/bin/env bash\n${marker}\n${lease}\nexec "/old/scripts/mosaic" "$@"\n`);
assert.equal(s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin]).status,0);
+ const lines=readFileSync(wrapper,'utf8').trimEnd().split('\n');
+ assert.equal(lines[1],marker);
+ assert.equal(lines[2],`# CLI runs from pinned copy: ${join(s.copies,s.id)}`);
+ assert.equal(lines[3],'# The Q81 lease probe still answers at mosaic-old.');
+ assert.equal(lines.at(-1),`exec "${join(s.copies,s.id)}/scripts/mosaic" "$@"`);
assert.match(readFileSync(wrapper,'utf8'),new RegExp(s.id));assert.ok(readFileSync(wrapper,'utf8').includes(lease));
const old=join(s.bin,'mosaic-old');writeFileSync(old,'#!/bin/sh\nprintf \"legacy:%s\\n\" \"$1\"\n');chmodSync(old,0o755);
assert.equal(spawnSync(wrapper,['__lease-capability'],{env:s.env,encoding:'utf8'}).stdout,'legacy:__lease-capability\n');
@@ -54,7 +59,11 @@ test('wrapper preserves Q81 lease and marker, rejects foreign/symlink; check use
s.fake(`echo 'LoadState=loaded'; echo 'WorkingDirectory=${copy}';case \"$*\" in *mosaic-bus*)echo 'ExecStart={ path=/other/scripts/mosaic ; argv[]=/other/scripts/mosaic bus start demo ; }';; *)echo 'ExecStart={ path=/usr/bin/node ; argv[]=/usr/bin/node ${copy}/packages/webui/src/cli.mjs serve ; }';;esac\n`);
assert.equal(s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin]).status,1,'effective ExecStart mismatch');
s.fake(`echo 'LoadState=loaded'; echo 'WorkingDirectory=/other';\n`);assert.equal(s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin]).status,1);
- const foreign=`#!/usr/bin/env bash\n# foreign wrapper\n${lease}\nexec \"/old/scripts/mosaic\" \"$@\"\n`;writeFileSync(wrapper,foreign);assert.notEqual(s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin]).status,0);assert.equal(readFileSync(wrapper,'utf8'),foreign);
+ const foreign=`#!/usr/bin/env bash\n# foreign wrapper\n${lease}\nexec \"${copy}/scripts/mosaic\" \"$@\"\n`;writeFileSync(wrapper,foreign);
+ // Restore matching effective roots: only the foreign marker refuses check.
+ s.fake(`echo 'LoadState=loaded';echo 'WorkingDirectory=${copy}';case "$*" in *mosaic-bus*) echo 'ExecStart={ path=${copy}/scripts/mosaic ; argv[]=${copy}/scripts/mosaic bus start demo ; }';; *)echo 'ExecStart={ path=/usr/bin/node ; argv[]=/usr/bin/node ${copy}/packages/webui/src/cli.mjs serve ; }';;esac\n`);
+ assert.equal(s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin]).status,1);
+ assert.notEqual(s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin]).status,0);assert.equal(readFileSync(wrapper,'utf8'),foreign);
const backing=join(s.bin,'backing');writeFileSync(backing,`#!/usr/bin/env bash\n${marker}\n${lease}\nexec \"/old/scripts/mosaic\" \"$@\"\n`);unlinkSync(wrapper);symlinkSync(backing,wrapper);assert.notEqual(s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin]).status,0);
});
test('bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal',t=>{
@@ -65,7 +74,7 @@ test('bus unit copy/pm pins, install refuses all busy states and failed reads, d
for(const state of ['active','activating','deactivating','reloading']){s.fake(`echo '[email protected] loaded ${state} running fixture';\n`);const r=s.run('bus-service.sh',['install',...args,'--dir',units,'--no-reload']);assert.notEqual(r.status,0,state);assert.ok(!existsSync(units));}
s.fake('exit 1\n');assert.notEqual(s.run('bus-service.sh',['install',...args,'--dir',units,'--no-reload']).status,0);assert.ok(!existsSync(units));
const dropdir=join(s.search,'[email protected]');mkdirSync(dropdir);const drop=join(dropdir,'pm.conf');writeFileSync(drop,'[Service]\nExecStart=elsewhere\n');
- s.fake(`case "$*" in *list-units*) echo '[email protected] loaded inactive dead fixture';; *show*) echo 'LoadState=loaded';echo 'DropInPaths=${drop}';; esac\n`);
+ s.fake(`case "$*" in *list-units*) echo '[email protected] loaded inactive dead fixture';; *)exit 99;; esac\n`);
const refused=s.run('bus-service.sh',['install',...args,'--dir',units,'--no-reload']);assert.notEqual(refused.status,0);assert.ok(refused.stderr.includes(drop));assert.ok(!existsSync(units));
writeFileSync(drop,'[Service]\nEnvironment=SAFE=1\n');assert.equal(s.run('bus-service.sh',['install',...args,'--dir',units,'--no-reload']).status,0);
});
@@ -151,7 +160,7 @@ test('drop-ins in either override field and malformed metadata refuse; uninstall
const s=setup(t),units=join(s.root,'units'),dropdir=join(s.search,'[email protected]');mkdirSync(dropdir);const drop=join(dropdir,'override.conf');
for(const field of ['ExecStart','WorkingDirectory']){
writeFileSync(drop,`[Service]\n${field}=override\n`);
- s.fake(`case "$*" in *list-units*) echo '[email protected] loaded inactive dead fixture';; *[email protected]*) echo 'DropInPaths=${drop}';; *)echo DropInPaths=;;esac\n`);
+ s.fake(`case "$*" in *list-units*) echo '[email protected] loaded inactive dead fixture';; *)exit 99;;esac\n`);
const r=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.notEqual(r.status,0);assert.ok(r.stderr.includes(drop));assert.ok(!existsSync(units));
}
s.fake('exit 0\n');s.analyze('exit 0\n');const unknown=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.notEqual(unknown.status,0);assert.match(unknown.stderr,/unknown/);
@@ -162,9 +171,11 @@ test('drop-ins in either override field and malformed metadata refuse; uninstall
test('install never shows a template name on systemd 261; template drop-ins still refuse',t=>{
const s=setup(t),units=join(s.root,'units'),calls=join(s.root,'systemctl-calls');
- s.fake(`echo "$*" >> '${calls}'\ncase "$*" in *show*[email protected]*) echo 'neither a valid invocation ID nor unit name' >&2; exit 1;; *list-units*) echo '[email protected] loaded inactive dead fixture';; *show*)echo DropInPaths=;; esac\n`);
+ s.fake(`echo "$*" >> '${calls}'\ncase "$*" in *show*[email protected]*) echo 'neither a valid invocation ID nor unit name' >&2; exit 1;; *list-units*) echo '[email protected] loaded inactive dead fixture';; *)exit 99;; esac\n`);
const result=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.equal(result.status,0,result.stderr);
assert.doesNotMatch(readFileSync(calls,'utf8'),/show mosaic-bus@\.service/);
+ // This rejection arm is deliberately exercised; it is not a dead show reply.
+ assert.equal(spawnSync(join(s.bin,'systemctl'),['--user','show','[email protected]'],{env:s.env}).status,1);
const dropdir=join(s.search,'[email protected]');mkdirSync(dropdir);const file=join(dropdir,'pm.conf');writeFileSync(file,'[Service]\nExecStart=override\n');
const before=readFileSync(join(units,'[email protected]'),'utf8');
const denied=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.notEqual(denied.status,0);assert.ok(denied.stderr.includes(file));assert.equal(readFileSync(join(units,'[email protected]'),'utf8'),before);
@@ -173,7 +184,29 @@ test('unlisted instance drop-in is found in another unit lookup directory; path
const s=setup(t),units=join(s.root,'units'),second=join(s.root,'second-search');mkdirSync(second);
const dropdir=join(second,'[email protected]');mkdirSync(dropdir);const file=join(dropdir,'cwd.conf');writeFileSync(file,'[Service]\nWorkingDirectory=/other\n');
s.analyze(`printf '%s\\n' '${s.search}' '${second}'\n`);
- s.fake("case \"$*\" in *list-units*) echo '[email protected] loaded inactive dead fixture';; *show*)echo DropInPaths=;; esac\n");
+ s.fake("case \"$*\" in *list-units*) echo '[email protected] loaded inactive dead fixture';; *)exit 99;; esac\n");
const denied=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.notEqual(denied.status,0);assert.ok(denied.stderr.includes(file));assert.ok(!existsSync(units));
s.analyze('exit 1\n');const failed=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.notEqual(failed.status,0);assert.match(failed.stderr,/unit-paths failed/);assert.ok(!existsSync(units));
});
+
+// Row 108: inherited drop-ins apply even without a loaded bus instance.
+for(const name of ['mosaic-.service.d','service.d'])test(`inherited ${name} override refuses without changing the unit`,t=>{
+ const s=setup(t),units=join(s.root,'units');mkdirSync(units);
+ const target=join(units,'[email protected]');writeFileSync(target,'previous unit bytes');
+ s.fake('exit 0\n');
+ const dropdir=join(s.search,name);mkdirSync(dropdir);const file=join(dropdir,'override.conf');
+ for(const field of ['WorkingDirectory','ExecStart']){
+ writeFileSync(file,`[Service]\n${field}=/other\n`);
+ const denied=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);
+ assert.equal(denied.status,1,denied.stderr);assert.ok(denied.stderr.includes(file));assert.equal(readFileSync(target,'utf8'),'previous unit bytes');
+ }
+ writeFileSync(file,'[Service]\nEnvironment=SAFE=1\n');
+ const allowed=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.equal(allowed.status,0,allowed.stderr);
+});
+test('inherited drop-in unreadable directory or file refuses before writing',t=>{
+ const s=setup(t),units=join(s.root,'units');mkdirSync(units);const target=join(units,'[email protected]');writeFileSync(target,'previous unit bytes');s.fake('exit 0\n');
+ const dropdir=join(s.search,'service.d');symlinkSync(join(s.root,'absent-dir'),dropdir);
+ const dir=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.equal(dir.status,1);assert.ok(dir.stderr.includes(dropdir));assert.equal(readFileSync(target,'utf8'),'previous unit bytes');
+ unlinkSync(dropdir);mkdirSync(dropdir);const file=join(dropdir,'broken.conf');symlinkSync(join(s.root,'absent-file'),file);
+ const broken=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.equal(broken.status,1);assert.ok(broken.stderr.includes(file));assert.equal(readFileSync(target,'utf8'),'previous unit bytes');
+});
diff --git a/scripts/bus-service.sh b/scripts/bus-service.sh
index 297c633b2..f71250204 100755
--- a/scripts/bus-service.sh
+++ b/scripts/bus-service.sh
@@ -7,8 +7,8 @@
# business (mosaic-bus@<business>). Nothing here reads a capability, the
# binding or the token. See packages/cli/README.md, "Bus host".
#
-# render [--claude PATH] print the rendered unit on stdout
-# install [--dir DIR] [--no-reload] [--claude PATH]
+# render [--copy ID [--copies DIR]] [--pm] [--claude PATH] print the rendered unit on stdout
+# install [--copy ID [--copies DIR]] [--pm] [--dir DIR] [--no-reload] [--claude PATH]
# write the unit (write to a temp file, then
# rename) and run `systemctl --user daemon-reload`
# uninstall [--dir DIR] [--no-reload]
@@ -115,7 +115,7 @@ install_guard() {
[ -e "$directory" ] || [ -L "$directory" ] || continue
[ -d "$directory" ] && [ -r "$directory" ] && [ -x "$directory" ] || die "refusing: cannot inspect unit path $directory; nothing was changed"
# * includes the empty instance, i.e. [email protected].
- for dropdir in "$directory"/mosaic-bus@*.service.d; do
+ for dropdir in "$directory"/mosaic-bus@*.service.d "$directory"/mosaic-.service.d "$directory"/service.d; do
[ -e "$dropdir" ] || [ -L "$dropdir" ] || continue
[ -d "$dropdir" ] && [ -r "$dropdir" ] && [ -x "$dropdir" ] || die "refusing: cannot inspect drop-ins $dropdir; nothing was changed"
for file in "$dropdir"/*.conf; do
diff --git a/scripts/stack-copy.sh b/scripts/stack-copy.sh
index a3b6f9143..f384a983d 100755
--- a/scripts/stack-copy.sh
+++ b/scripts/stack-copy.sh
@@ -66,7 +66,7 @@ wrapper() {
[ "$(grep -Fxc "$lease" "$file" || true)" = 1 ] || die 'refusing: Q81 lease line missing or ambiguous'
tmp="$(mktemp "$BIN/.mosaic.XXXXXX")"
trap 'rm -f "$tmp"' EXIT
- printf '#!/usr/bin/env bash\n%s\n%s\nexec "%s/scripts/mosaic" "$@"\n' "$MARKER" "$lease" "$root" > "$tmp"
+ printf '#!/usr/bin/env bash\n%s\n# CLI runs from pinned copy: %s\n# The Q81 lease probe still answers at mosaic-old.\n%s\nexec "%s/scripts/mosaic" "$@"\n' "$MARKER" "$root" "$lease" "$root" > "$tmp"
chmod 0755 "$tmp";mv -f "$tmp" "$file";trap - EXIT
echo "wrapper: $root"
}
@@ -0,0 +1,6 @@
891b7543a85dee16332ce72e75be12d34e37f4cd9a5d46047a71e8a4f89e512c docs/TOOLS.md
c9910fe249fd1dc09dd370e51022fd377664627f8a89f4a9cd440cb087630591 packages/cli/README.md
4fe719a1ddca8ca876ba44024c777d32c65d1c13e21de06155b1cca6393e2e43 packages/cli/tests/host.test.mjs
b64eadec44ff8ddaf8b21520dd5c7db83d1ad0d34719565b8f2ca6c22de4bdb3 packages/cli/tests/stack-copy.test.mjs
cf3aff18d0b6b7befa3b7c99e01703bccafe83aee61fd34537cc884543a161ec scripts/bus-service.sh
3a25251516fe24ce47a9027c1bfcbc7542fe56aba1ec66ad3d968ac196d7e64a scripts/stack-copy.sh
+6
View File
@@ -0,0 +1,6 @@
docs/TOOLS.md
packages/cli/README.md
packages/cli/tests/host.test.mjs
packages/cli/tests/stack-copy.test.mjs
scripts/bus-service.sh
scripts/stack-copy.sh
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
"""Run shell suites from a detached worktree, capture every exit."""
import pathlib, subprocess, json, datetime, sys, os
root=pathlib.Path.cwd()
if root == pathlib.Path('/mnt/storage/src/mosaic-stack'):
raise SystemExit('gate refuses canonical checkout')
if subprocess.check_output(['git','branch','--show-current'],text=True).strip():
raise SystemExit('gate requires detached HEAD')
if os.environ.get('DOCKER_HOST') != 'unix:///nonexistent.sock':
raise SystemExit('decision 127 requires DOCKER_HOST=unix:///nonexistent.sock')
out=pathlib.Path(sys.argv[1]); out.mkdir(parents=True,exist_ok=True)
results=[]
commands=[]
for package in [root/'packages/cli']:
tests=sorted(package.glob('tests/**/*.test.mjs'))
if tests: commands.append(('node-'+package.name,['node','--test',*[str(t.relative_to(root)) for t in tests]],600))
for suite in sorted(root.glob('scripts/test-*.sh')):
commands.append((suite.stem,['bash',str(suite)],600))
for name,command,timeout in commands:
began=datetime.datetime.now(datetime.timezone.utc).isoformat()
with (out/(name+'.txt')).open('w') as log:
p=subprocess.run(command,cwd=root,stdout=log,stderr=subprocess.STDOUT,timeout=timeout)
row={'suite':name,'exit':p.returncode,'began':began,'ended':datetime.datetime.now(datetime.timezone.utc).isoformat()}
results.append(row)
(out/'gate-results.json').write_text(json.dumps(results,indent=2)+'\n')
print(json.dumps(row),flush=True)
sys.exit(any(r['exit'] for r in results))
+27
View File
@@ -0,0 +1,27 @@
import pathlib,subprocess,sys,json,os,signal
root=pathlib.Path.cwd();out=pathlib.Path(sys.argv[1]);out.mkdir(parents=True,exist_ok=True)
assert root != pathlib.Path('/mnt/storage/src/mosaic-stack')
assert os.environ.get('DOCKER_HOST')=='unix:///nonexistent.sock'
bus='scripts/bus-service.sh';stack='scripts/stack-copy.sh';test='packages/cli/tests/stack-copy.test.mjs'
muts=[
('miss-prefix',bus,' "$directory"/mosaic-.service.d','', 'inherited mosaic'),
('miss-all-services',bus,' "$directory"/service.d','', 'inherited service'),
('ignore-unreadable-directory',bus,'[ -d "$dropdir" ] && [ -r "$dropdir" ] && [ -x "$dropdir" ] || die',': || die','inherited drop-in unreadable'),
('ignore-unreadable-file',bus,'content="$(cat "$file" 2>/dev/null)" || die "refusing: cannot read drop-in $file; nothing was changed"','content="$(cat "$file" 2>/dev/null)" || content=""','inherited drop-in unreadable'),
('omit-copy-comment',stack,'# CLI runs from pinned copy: %s','# CLI copy unspecified: %s','wrapper preserves'),
('omit-lease-comment',stack,'# The Q81 lease probe still answers at mosaic-old.','# Lease explanation omitted.','wrapper preserves'),
('accept-foreign-check-wrapper',stack,'[ "$(sed -n \'2p\' "$BIN/mosaic")" = "$MARKER" ]; then','[ 1 = 1 ]; then','wrapper preserves'),
]
results=[]
for name,file,old,new,pattern in muts:
p=root/file;original=p.read_text();assert original.count(old)==1,(name,original.count(old))
try:
p.write_text(original.replace(old,new,1))
with (out/(name+'.txt')).open('w') as log:
child=subprocess.Popen(['node','--test','--test-name-pattern='+pattern,test],stdout=log,stderr=subprocess.STDOUT,start_new_session=True)
try:code=child.wait(timeout=60);status='killed' if code else 'survived'
except subprocess.TimeoutExpired:os.killpg(child.pid,signal.SIGKILL);child.wait();code=124;status='timeout'
row={'name':name,'exit':code,'status':status};results.append(row);print(json.dumps(row),flush=True)
(out/'summary.json').write_text(json.dumps(results,indent=2)+'\n')
finally:p.write_text(original)
sys.exit(any(r['status']!='killed' for r in results))
@@ -0,0 +1 @@
6/6 candidate hashes match after gate and mutants; patch equals detached worktree diff; canonical apply --check passes; git diff --check clean; logs-only commit d7430483626f82c8c8c39b84cae03925d2c811bb; no source commit.
@@ -0,0 +1,68 @@
[
{
"suite": "node-cli",
"exit": 0,
"began": "2026-10-14T20:05:00.721454+00:00",
"ended": "2026-10-14T20:05:13.966268+00:00"
},
{
"suite": "test-auth",
"exit": 0,
"began": "2026-10-14T20:05:13.966534+00:00",
"ended": "2026-10-14T20:05:14.581652+00:00"
},
{
"suite": "test-conductor",
"exit": 0,
"began": "2026-10-14T20:05:14.581919+00:00",
"ended": "2026-10-14T20:05:19.256693+00:00"
},
{
"suite": "test-config",
"exit": 0,
"began": "2026-10-14T20:05:19.256945+00:00",
"ended": "2026-10-14T20:05:20.876187+00:00"
},
{
"suite": "test-discord",
"exit": 0,
"began": "2026-10-14T20:05:20.876446+00:00",
"ended": "2026-10-14T20:05:27.405639+00:00"
},
{
"suite": "test-extension-package",
"exit": 0,
"began": "2026-10-14T20:05:27.405892+00:00",
"ended": "2026-10-14T20:05:28.722298+00:00"
},
{
"suite": "test-foundation",
"exit": 0,
"began": "2026-10-14T20:05:28.722555+00:00",
"ended": "2026-10-14T20:06:07.275537+00:00"
},
{
"suite": "test-land-work",
"exit": 0,
"began": "2026-10-14T20:06:07.275850+00:00",
"ended": "2026-10-14T20:06:16.059005+00:00"
},
{
"suite": "test-queue",
"exit": 0,
"began": "2026-10-14T20:06:16.059326+00:00",
"ended": "2026-10-14T20:06:36.263997+00:00"
},
{
"suite": "test-release",
"exit": 0,
"began": "2026-10-14T20:06:36.264278+00:00",
"ended": "2026-10-14T20:06:36.428496+00:00"
},
{
"suite": "test-task",
"exit": 0,
"began": "2026-10-14T20:06:36.428788+00:00",
"ended": "2026-10-14T20:06:38.397111+00:00"
}
]
@@ -0,0 +1,197 @@
✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (1324.255253ms)
✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (73.164371ms)
✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (161.886353ms)
✔ NO_COLOR: no escape codes, except reverse video on the selected row (258.751578ms)
✔ 1-9 shows the decide flow's own lines and question; only y resolves (55.31198ms)
✔ the app shows the same decide text as mosaic decide (34.108291ms)
✔ any key but y or Y at the question answers No; nothing is sent (224.453199ms)
✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (104.416927ms)
✔ under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10) (377.281955ms)
✔ a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12) (38.416063ms)
✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (29.237208ms)
✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (443.098483ms)
✔ quitting at the question answers No and sends nothing, armed or not (54.023187ms)
✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (47.501421ms)
✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (64.057095ms)
✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (419.339807ms)
✔ at every size the decide box shows each line y depends on, or says it is hidden (8529.753063ms)
✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (62.276549ms)
✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (29.049886ms)
✔ help or a detail box cut to fit keeps its last line, Esc included (Dewey) (13.898205ms)
✔ keys: views, moving, trail of a selection, the command line, help and quit (71.484737ms)
✔ one option: the status line says Press 1, not 1-1 (14.04437ms)
✔ no business: the app says so and :business picks one (7.943052ms)
✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (1.742299ms)
✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (166.59363ms)
✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.437712ms)
✔ decide with --note still asks; a No sends nothing (9.437952ms)
✔ the app's business comes from a live bus host only (1.037954ms)
✔ runApp leaves the terminal as it found it on q, end of input, SIGTERM, SIGINT, SIGHUP and an error, the first frame's included (6.879287ms)
✔ draw is pure: same state, same frame (0.785457ms)
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (28.941921ms)
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (27.735949ms)
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (21.86062ms)
✔ decide prints a declining choice as declining (22.183401ms)
✔ an unknown outcome is reported once and never resent (16.490674ms)
✔ a decision closed before the answer arrives exits 2 and points at its trail (16.6485ms)
✔ a prefix that matches two open decisions exits 2 and resolves neither (14.568569ms)
✔ without --business a command uses the live host's business, and a stale host.json is not a host (13.204451ms)
✔ every human command refuses inside an agent run before it touches the bus (12.099752ms)
✔ usage errors exit 4; no business and no host is a usage error (14.992015ms)
✔ agents and tasks print through the broker (13.28199ms)
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.009071ms)
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (72.200723ms)
✔ trackers come from the tracker.* variables of the one project that names a tracker project (100.274785ms)
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (41.938935ms)
✔ two projects that each name a tracker project refuse, since the boot shape holds one (44.839011ms)
✔ a business without tracker.baseUrl gets no trackers entry (42.013242ms)
✔ an unknown business and a broken system config refuse with exit 3 (64.809989ms)
✔ follow prints an unsolicited message after plain talk returned (42.966398ms)
✔ talk --follow continues after reply and prints a later unsolicited message (36.903114ms)
✔ inbox lists all unread human messages under decisions, does not deliver or read them (298.901768ms)
✔ follow refuses agent context and bad arguments (8.125493ms)
✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (12.661688ms)
✔ plain talk leaves the process SIGINT handlers unchanged (10.937406ms)
✔ empty views say so (1.129747ms)
✔ the trail keeps the broker's order and names a decision's task without its rows (1.566478ms)
✔ tasks print the tracker fields the snapshot carries (0.2397ms)
✔ the review loop: the coder pushes and asks, the reviewer fetches and approves, and the task's trail holds each step (459.454493ms)
✔ refusals: no action, non-fast-forward, an unknown commit or request, and a push for another task moves only that task's ref (289.884086ms)
✔ the size cap refuses a push before any byte (143.94549ms)
✔ a fetch over the cap refuses before any byte reaches the reviewer (321.951612ms)
✔ the socket refuses a request with no capability, an unknown one, and a bad request line (162.728107ms)
✔ a rigged clone runs nothing: hooks, fsmonitor, a filter, a gitlink and a symlinked .git/config leave no marker (264.961547ms)
✔ an identity with no role, or of another business, refuses not-a-role and moves no ref (212.942363ms)
✔ a review of a project the business no longer has refuses unknown-project (204.401342ms)
✔ past maxActive connections the socket refuses git-busy, and a hung-up connection frees its slot (168.319256ms)
✔ close() answers host-closing to a connection still sending its request line (179.982033ms)
✔ a bundle that trickles in refuses body-timeout at its deadline and frees its slot (617.669913ms)
✔ a reviewer gone before its bundle was sent is filed fetch-aborted, not as the host's fault (598.213203ms)
✔ a reviewer that hangs up the moment it has the whole bundle was served, not aborted (674.069328ms)
✔ send: a close after every byte left is served; one that fails a queued write is fetch-aborted (41.934417ms)
✔ a bundle with no bytes for bodyIdle refuses body-timeout long before its deadline (484.096653ms)
✔ send: a close once the loop is past its last chunk is served, and a failed write there is fetch-aborted (44.70515ms)
✔ letGo waits for an answer to leave for at most its cap, then destroys the socket (302.466069ms)
✔ a record that fails and can't be undone refuses rollback-failed, which the host logs (334.245772ms)
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (926.2985ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (144.864941ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (77.793275ms)
✔ two bus starts racing for one data root leave one host running; the other refuses that a host already runs (479.472687ms)
✔ a host takes over a host.lock its dead host left, and puts back a live claim that replaced it meanwhile (1.515899ms)
✔ 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.847609ms)
✔ a live host's lock stays, however old the file is (0.572998ms)
✔ a host.lock left by a host killed with SIGKILL doesn't stop the next bus start (822.870952ms)
✔ while a host boots, a reader sees no host: the human commands still ask for --business, stop and status find none (264.279654ms)
✔ a host started before host.lock existed, holding only host.json, still refuses a second start, which leaves no lock (39.650988ms)
✔ a broker fork that throws leaves no host.lock and no host.json (36.428573ms)
✔ a broker send that throws leaves no host.lock and no host.json (38.329846ms)
✔ a broker that misses its boot wait leaves no host.lock and no host.json (43.834281ms)
✔ a notifier fork that throws stops the broker and leaves no host.lock and no host.json (140.953834ms)
✔ a notifier send that throws stops the broker and leaves no host.lock and no host.json (124.148834ms)
✔ 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 (114.940435ms)
✔ 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 (122.512177ms)
✔ a host.json that can't be written stops both children and leaves no host.lock (267.206694ms)
✔ an error event on the broker before it replies fails the start at once, and leaves no host.lock and no live child (42.38589ms)
✔ an error event on the notifier before it replies fails the start at once, and leaves no host.lock and no live child (109.73294ms)
✔ an error event on the broker while the notifier starts fails the start once the notifier replies (292.115473ms)
✔ 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 (287.331653ms)
✔ an error event on the notifier once the host is up stops the host with exit 1, and leaves no host.lock and no host.json (259.641899ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1118.402795ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (177.746672ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (76.67044ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (131.130488ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (125.642606ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (77.176599ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (130.157155ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (78.190572ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (148.177ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (21.234097ms)
✔ bus stop refuses to signal a live pid that is not a bus host (202.638255ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (253.144743ms)
✔ bus start refuses with exit 3 without a notifier config (112.345897ms)
✔ bus start runs until bus stop; status reports it while it runs (693.66935ms)
✔ bus-service.sh renders the unit and installs it into a given directory (55.424401ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (69.120281ms)
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (12.928652ms)
✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (8.238887ms)
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1259.91577ms)
✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (421.993272ms)
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (3682.176739ms)
✔ a project clone that can't be made refuses checkout-failed, from --pm and from a session, with no run and no clone left (111.127903ms)
✔ a runner that stops at once ends its launch with the runner's reason (186.299304ms)
✔ a Claude Code launch with no session credential refuses before it starts (94.110448ms)
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (187.408127ms)
✔ a runner killed from the host ends its launch as killed, not failed (227.753219ms)
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (99.764384ms)
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1198.642345ms)
✔ 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 (590.515632ms)
✔ 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 (3530.185396ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (81.930017ms)
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (89.919353ms)
✔ a launch client that never closes its side doesn't hold the host's close (86.185996ms)
✔ a runner that ignores SIGTERM is killed when the host closes (1197.271782ms)
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (4.322061ms)
✔ zoned uses the IANA zone across DST (25.906922ms)
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (30.887735ms)
✔ two blocking decisions get two DMs with different nonces (21.783906ms)
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.273831ms)
✔ a failed DM is journaled, backs off, and is retried until it lands (21.084358ms)
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (24.571308ms)
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (29.734616ms)
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (17.793905ms)
✔ 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 (113.895861ms)
✔ a restart after the second refusal does not send before that refusal's 30 min are up (45.076869ms)
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (14.015711ms)
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (10.272029ms)
✔ an inbox read failure is logged and the next poll retries (0.736646ms)
✔ no Discord id reaches the journal or the log (11.319672ms)
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.289477ms)
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.379193ms)
✔ the journal: a whole file that is one torn line truncates to empty (1.562892ms)
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.566013ms)
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.786074ms)
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.432193ms)
✔ the journal: a symlinked directory refuses and says it is a link (0.407908ms)
✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (2.05593ms)
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.44111ms)
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.462544ms)
✔ digest content stays within Discord's 2000 characters (0.264637ms)
✔ runLoop never overlaps ticks and stops after the one in flight (111.299932ms)
✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (507.03983ms)
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (402.394593ms)
✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (413.745167ms)
✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (276.296237ms)
✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (4239.076738ms)
✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (464.792063ms)
✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (170.42422ms)
✔ install never shows a template name on systemd 261; template drop-ins still refuse (165.159816ms)
✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (147.646857ms)
✔ inherited mosaic-.service.d override refuses without changing the unit (169.98802ms)
✔ inherited service.d override refuses without changing the unit (175.813221ms)
✔ inherited drop-in unreadable directory or file refuses before writing (135.769183ms)
✔ talk --tester sends a [mos test] message as tester from inside an agent and prints the role's reply (36.774632ms)
✔ talk --tester refuses text without the [mos test] prefix before any call (17.644945ms)
✔ the read verbs take --tester inside an agent, and the trail shows the tester as the sender (25.032622ms)
✔ decide, stop and launches take no --tester (16.261267ms)
✔ the tester transport runs tester-cli.mjs, and the tester's refusals exit 3 (0.416855ms)
✔ with no stand-in transport, --tester runs tester-cli.mjs on the data root's socket (0.739971ms)
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 (324.44889ms)
✔ the transport writes {business, verb, args} to the child and reads its JSON (53.800157ms)
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2209.810815ms)
✔ busExit and refuseInsideAgent (0.8034ms)
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (56.585616ms)
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1039.018927ms)
✔ a late reply the Console has listed still reaches the next talk (2031.309429ms)
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (17.228178ms)
✔ launches off and on go to the broker and change the business's launch state (15.619332ms)
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (17.161962ms)
ℹ tests 187
ℹ suites 0
ℹ pass 187
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 13212.307403
@@ -0,0 +1,17 @@
OK status with missing harness credential exits 3 and still lists accounts
OK status reports harness credential (read-only) + mosaic accounts
OK api key material never reaches output
OK oauth token material never reaches output
OK unparseable credential file exits 2
OK symlinked credential file exits 4
OK env-side credential names reported
OK env var values never reach output
OK accounts without an accounts dir reports none and creates nothing
OK accounts lists files and marks the active one
OK loose account perms flagged in listing
OK agent --auth with missing account file refuses (exit 4)
OK agent --auth with non-0600 account file refuses
OK agent --auth with invalid account name refuses
OK auth.sh without valid config refuses
selftest: 15 passed, 0 failed
@@ -0,0 +1,23 @@
On branch refactor
Your branch is up to date with 'origin/refactor'.
nothing to commit, working tree clean
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
OK dry-run committed nothing
OK apply: allowed change exits 0 (exit 0)
OK apply: attribution in commit subject
OK apply: target tree clean after commit
OK disallowed path refused (exit 1)
OK disallowed path: target untouched
OK syntax gate refused broken .mjs (exit 1)
OK syntax gate: target untouched
OK suite failure refused (exit 1)
OK suite failure: target reverted to clean
OK disabled policy refused (exit 2)
OK disabled policy: target untouched
OK failed run refused (exit 1)
OK failed run: target untouched
OK missing run exits 4 (exit 4)
OK invalid policy exits 2 (exit 2)
selftest: 17 passed, 0 failed
@@ -0,0 +1,26 @@
OK absent adapter defaults to pi
OK adapter mock validates (exit 0)
OK unsupported adapter exits 2 (exit 2)
OK env exports adapter
OK bootstrap creates default when absent (exit 0)
OK bootstrap wrote config file
OK bootstrap is idempotent on existing config (exit 0)
OK bootstrap did not rewrite existing config
OK validate missing config exits 3 (exit 3)
OK malformed JSON exits 2 (exit 2)
OK unsupported configVersion exits 2 (exit 2)
OK unknown top-level key exits 2 (exit 2)
OK unknown execution key exits 2 (exit 2)
OK unsupported backend exits 2 (exit 2)
OK unsupported environment exits 2 (exit 2)
OK relative dataRoot exits 2 (exit 2)
OK non-canonical dataRoot exits 2 (exit 2)
OK filesystem root dataRoot exits 2 (exit 2)
OK home directory dataRoot exits 2 (exit 2)
OK dataRoot containing config dir exits 2 (exit 2)
OK control character in provider exits 2 (exit 2)
OK symlinked config file exits 2 (exit 2)
OK env exports resolve correctly
OK failed validation modified nothing
selftest: 24 passed, 0 failed
@@ -0,0 +1,70 @@
toolchain: node v26.8.1
OK syntax: packages/discord/src/approvals.mjs
OK syntax: packages/discord/src/authorize.mjs
OK syntax: packages/discord/src/binding.mjs
OK syntax: packages/discord/src/cli.mjs
OK syntax: packages/discord/src/connector.mjs
OK syntax: packages/discord/src/context.mjs
OK syntax: packages/discord/src/engine-pi.mjs
OK syntax: packages/discord/src/errors.mjs
OK syntax: packages/discord/src/gateway.mjs
OK syntax: packages/discord/src/git.mjs
OK syntax: packages/discord/src/journal.mjs
OK syntax: packages/discord/src/notify.mjs
OK syntax: packages/discord/src/rest.mjs
OK syntax: packages/discord/src/setspark.mjs
OK syntax: packages/discord/src/tools.mjs
OK syntax: packages/discord/src/web.mjs
OK syntax: packages/discord/bin/git-credential.mjs
OK syntax: packages/discord/extension/tools.mjs
OK syntax: packages/discord/tests/approvals.test.mjs
OK syntax: packages/discord/tests/authorize.test.mjs
OK syntax: packages/discord/tests/binding.test.mjs
OK syntax: packages/discord/tests/connector.test.mjs
OK syntax: packages/discord/tests/context.test.mjs
OK syntax: packages/discord/tests/engine.test.mjs
OK syntax: packages/discord/tests/fake-pi.mjs
OK syntax: packages/discord/tests/gateway.test.mjs
OK syntax: packages/discord/tests/git.test.mjs
OK syntax: packages/discord/tests/helpers.mjs
OK syntax: packages/discord/tests/journal.test.mjs
OK syntax: packages/discord/tests/notify.test.mjs
OK syntax: packages/discord/tests/recover.test.mjs
OK syntax: packages/discord/tests/rest.test.mjs
OK syntax: packages/discord/tests/setspark.test.mjs
OK syntax: packages/discord/tests/tools.test.mjs
OK syntax: packages/discord/tests/web.test.mjs
OK syntax: packages/discord/fixtures/claim-worker.mjs
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
OK syntax: scripts/discord.sh
OK syntax: scripts/discord-service.sh
OK packages/discord declares no dependencies
OK no bot-token-shaped string in packages/discord
OK fixture binding uses placeholder ids only
OK fixture binding validates
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
OK real pi with protocol vault adds reserve_id to the git verbs
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
OK real pi refuses a git key on a read-only root (fail closed)
OK real pi with the pilot flags (--no-tools) exposes no tool at all
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test packages/discord/tests/ (ℹ pass 178)
OK scripts/discord.sh --help exits 0
OK scripts/discord.sh check without a binding exits 4
OK scripts/discord.sh recover without a binding exits 4
OK scripts/discord.sh reload without a binding exits 4
OK scripts/discord-service.sh without a command exits 4
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
OK service install writes the rendered unit (0644) and leaves no temp file
OK service install a second time reports unchanged
OK systemd-analyze verify accepts the rendered unit
OK service uninstall removes the unit file
OK service install with an unknown flag exits 4
OK service install with USER unset finishes and names the account for lingering
discord suite: 66 passed, 0 failed
@@ -0,0 +1,21 @@
OK initial ordinary-file install
OK installed tree matches canonical source
OK installed tree has no symlinks
OK check detects installation drift
OK sync refuses to overwrite installation drift
OK check detects an extra destination file
OK check detects an extra destination directory
OK check rejects a destination symlink
OK sync accepts a canonical source update
OK updated installation matches canonical source
/mnt/storage/src/mosaic-stack-rocko-108/scripts/test-extension-package.sh: line 14: 1488135 Killed "$@" > /dev/null 2>&1
OK forced interruption kills the replacing process
OK next invocation recovers old consistent installation
OK interrupted replacement rolled back
OK sync succeeds after interruption recovery
OK unlocked stale lock file does not block
OK active lock refuses a concurrent sync
OK source symlink fails closed
OK nested second entrypoint fails closed
extension package selftest: 18 passed, 0 failed
@@ -0,0 +1,53 @@
toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0
OK syntax: scripts/foundation-inspect.mjs
OK syntax: scripts/foundation/strict-json.mjs
OK syntax: scripts/foundation/canonical.mjs
OK syntax: scripts/foundation/resolve.mjs
OK syntax: scripts/foundation/validate-record.mjs
OK syntax: scripts/foundation/fixtures/build-fixtures.mjs
OK syntax: scripts/foundation/canonical.test.mjs
OK syntax: scripts/foundation/cli.test.mjs
OK syntax: scripts/foundation/fixtures.test.mjs
OK syntax: scripts/foundation/resolve.test.mjs
OK syntax: scripts/foundation/strict-json.test.mjs
OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)
OK fixture generator runs
OK checked-in fixtures/bundles equal a fresh generation
OK checked-in fixtures/raw equal a fresh generation
OK checked-in fixtures/index.json equal a fresh generation
OK checked-in demo bundles equal a fresh generation
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test scripts/foundation/ (ℹ pass 80)
OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)
platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999)
node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases)
schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
profile column (schema-valid records only): profile-valid 510, profile-invalid 30
profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns
OK oracle: zero schema-column disagreements with the pinned checker
OK oracle: strict-only profile refusals are counted and asserted
OK demo: permitted read preview exits 0 (exit 0)
OK demo: permitted file.change preview exits 0 (exit 0)
OK demo: assignment.change proposal is unresolved (exit 3) (exit 3)
OK demo: revoked registration is refused (exit 3) (exit 3)
OK demo: message is not authority (exit 3) (exit 3)
OK usage: no arguments exits 2 (exit 2)
OK io: missing file exits 4 (exit 4)
OK io: directory exits 4 (exit 4)
OK io: symlink exits 4 (O_NOFOLLOW) (exit 4)
OK bound: oversize fixture exits 2 (exit 2)
OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2)
OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2)
OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0)
OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed
OK text output starts with the disclaimer
OK json output is valid JSON with result allowed and exactly the charter §7 fields
OK json golden matches byte-for-byte
OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal
OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs
OK canary never printed (bundle run and credential-file run)
OK a non-bundle JSON file is refused at the shape gate, not read into output
OK no field of the non-bundle file is echoed
selftest: 44 passed, 0 failed
@@ -0,0 +1,86 @@
OK args: exit 2, target-branch
OK args: exit 2, target-branch
OK args: exit 4
OK args: exit 4
OK args: exit 4
OK --base next refuses before the config is read (got 2)
OK a missing system config exits 3 (got 3)
OK nobus: exit 3
OK schema-table: exit 2, bus-schema
OK schema-meta: exit 2, bus-schema
OK openBus opens the database read-only
OK other-business: exit 2, decision-not-found
OK no-raise: exit 2, decision-not-found
OK not-a-merge: exit 2, not-a-merge
OK open: exit 2, not-resolved
OK withdrawn: exit 2, not-resolved
OK by-agent: exit 2, not-human
OK via-webui: exit 2, not-human
OK no-answer: exit 2, not-human
OK answer-role: exit 2, not-human
OK answer-run: exit 2, not-human
OK answer-choice: exit 2, not-human
OK two-answers: exit 2, not-human
OK chose-no: exit 2, not-merge-option
OK two-refs: exit 2, work-ref
OK bad-ref: exit 2, work-ref
OK task-ref-disagrees: exit 2, work-ref
OK raise-task-disagrees: exit 2, work-ref
OK target-main: exit 2, target-branch
OK superseded-question: exit 2, superseded
OK superseded-task: exit 2, superseded
OK superseded-by: exit 2, superseded
OK no-verdict: exit 2, no-verdict
OK changes-later: exit 2, not-approved
OK short-commit: exit 2, not-approved
OK wrong-task-body: exit 2, not-approved
OK args: exit 2, decision-not-found
OK earlier-merge: exit 0
OK ref-moved: exit 2, ref-moved
OK root-top: exit 2, root-file
OK root-newdir: exit 2, root-file
OK root-src: exit 2, root-file
OK unrelated: exit 2, base-conflict
OK empty: exit 2, base-conflict
OK an empty reviewed diff says so
OK dirty-changed: exit 2, checkout-dirty
OK dirty-staged: exit 2, checkout-dirty
OK dirty-untracked: exit 2, checkout-dirty
OK conflict: exit 2, base-conflict
OK a refusal after the worktree is added leaves no worktree or scratch
OK cleanup looks for the worktree's Docker network
OK dirty-during: exit 2, checkout-dirty
OK a reviewed path dirtied during the gate moves no ref
OK drift: exit 2, patch-drift
OK patch-drift leaves no worktree or scratch
OK gate-pkg: exit 2, gate-failed
OK gate-failed names the failing step's log, and it exists
OK gate-failed leaves no worktree or scratch
OK gate-script: exit 2, gate-failed
OK a failing scripts/test-*.sh fails the gate
OK a failed gate moves no ref
OK nomods: exit 4
OK a target without node_modules leaves no worktree or scratch
OK moved: exit 2, base-moved
OK base-moved keeps the base where it moved to
OK dry: exit 0
OK --dry-run writes no ref
OK --dry-run prints the commit with --subject and --attribution
OK --dry-run runs the gate
OK --dry-run leaves no worktree or scratch
OK land: exit 0
OK the landing is one commit on the old base
OK its diff is the reviewed diff by patch-id
OK its tree is the dry run's
OK the message has the option's subject, the citations and the attribution last
OK it prints the line for the PM
OK the checked-out base has the reviewed paths and keeps the unrelated edit
OK only refs/heads/refactor exists in the target
OK the work ref is unchanged
OK the bus database is unchanged
OK the gate ran each package suite but queue, and each scripts/test-*.sh
OK the landing leaves no worktree or scratch
OK the target's node_modules is still there, with its contents
OK the worktree's Docker network is removed
---
land-work selftests: 84 passed, 0 failed
@@ -0,0 +1,36 @@
toolchain: node v26.8.1, git version 2.55.0
OK syntax: packages/queue/src/cli.mjs
OK syntax: packages/queue/src/errors.mjs
OK syntax: packages/queue/src/io.mjs
OK syntax: packages/queue/src/lock.mjs
OK syntax: packages/queue/src/queue.mjs
OK syntax: packages/queue/src/review.mjs
OK syntax: packages/queue/src/store.mjs
OK syntax: packages/queue/tests/commit.test.mjs
OK syntax: packages/queue/tests/data.test.mjs
OK syntax: packages/queue/tests/dispatch.test.mjs
OK syntax: packages/queue/tests/gate-owner.test.mjs
OK syntax: packages/queue/tests/helpers.mjs
OK syntax: packages/queue/tests/lock.test.mjs
OK syntax: packages/queue/tests/migration.test.mjs
OK syntax: packages/queue/tests/review.test.mjs
OK syntax: packages/queue/tests/store.test.mjs
OK syntax: packages/queue/tests/write.test.mjs
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
OK syntax: scripts/queue-commit.sh
OK syntax: scripts/git-hooks/pre-commit
OK syntax: scripts/mosaic
OK queue-commit.sh, the guard and scripts/mosaic are executable
OK packages/queue declares no dependencies
ℹ tests 156
ℹ pass 156
ℹ fail 0
OK node --test packages/queue/tests/
OK scripts/mosaic queue help
skip queue verify and render --check: this checkout (/mnt/storage/src/mosaic-stack-rocko-108) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
queue suite: 28 passed, 0 failed
@@ -0,0 +1,7 @@
OK valid RELEASE resolves (exit 0)
OK invalid RELEASE exits 1 (exit 1)
OK missing RELEASE exits 1 (exit 1)
OK valid RELEASE leaves image tag consistent with version
skip state-machine cases (docker daemon unavailable)
selftest: 4 passed, 0 failed
@@ -0,0 +1,32 @@
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)
skip adapter seam cases (docker daemon unavailable)
skip workspace/capability cases (docker daemon unavailable)
skip live task cases (docker unavailable)
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
skip live recall pair: user recall run, recalled user name (docker daemon unavailable)
OK no agent identity on headless run
selftest: 26 passed, 0 failed
@@ -0,0 +1,68 @@
[
{
"suite": "node-cli",
"exit": 0,
"began": "2026-10-14T20:08:16.669752+00:00",
"ended": "2026-10-14T20:08:29.916793+00:00"
},
{
"suite": "test-auth",
"exit": 0,
"began": "2026-10-14T20:08:29.917023+00:00",
"ended": "2026-10-14T20:08:30.532205+00:00"
},
{
"suite": "test-conductor",
"exit": 0,
"began": "2026-10-14T20:08:30.532452+00:00",
"ended": "2026-10-14T20:08:35.257973+00:00"
},
{
"suite": "test-config",
"exit": 0,
"began": "2026-10-14T20:08:35.258217+00:00",
"ended": "2026-10-14T20:08:36.927811+00:00"
},
{
"suite": "test-discord",
"exit": 0,
"began": "2026-10-14T20:08:36.928073+00:00",
"ended": "2026-10-14T20:08:43.506603+00:00"
},
{
"suite": "test-extension-package",
"exit": 0,
"began": "2026-10-14T20:08:43.506874+00:00",
"ended": "2026-10-14T20:08:44.823470+00:00"
},
{
"suite": "test-foundation",
"exit": 0,
"began": "2026-10-14T20:08:44.823736+00:00",
"ended": "2026-10-14T20:09:21.468582+00:00"
},
{
"suite": "test-land-work",
"exit": 0,
"began": "2026-10-14T20:09:21.468850+00:00",
"ended": "2026-10-14T20:09:29.950720+00:00"
},
{
"suite": "test-queue",
"exit": 0,
"began": "2026-10-14T20:09:29.951035+00:00",
"ended": "2026-10-14T20:09:50.306428+00:00"
},
{
"suite": "test-release",
"exit": 0,
"began": "2026-10-14T20:09:50.306714+00:00",
"ended": "2026-10-14T20:09:50.420924+00:00"
},
{
"suite": "test-task",
"exit": 0,
"began": "2026-10-14T20:09:50.421163+00:00",
"ended": "2026-10-14T20:09:52.390220+00:00"
}
]
@@ -0,0 +1,197 @@
✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (1477.376378ms)
✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (68.456805ms)
✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (173.578768ms)
✔ NO_COLOR: no escape codes, except reverse video on the selected row (250.133787ms)
✔ 1-9 shows the decide flow's own lines and question; only y resolves (54.740106ms)
✔ the app shows the same decide text as mosaic decide (23.028442ms)
✔ any key but y or Y at the question answers No; nothing is sent (198.172467ms)
✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (62.782956ms)
✔ under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10) (270.375393ms)
✔ a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12) (32.954214ms)
✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (21.639014ms)
✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (404.232785ms)
✔ quitting at the question answers No and sends nothing, armed or not (46.718151ms)
✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (33.5244ms)
✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (54.356483ms)
✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (335.035339ms)
✔ at every size the decide box shows each line y depends on, or says it is hidden (8489.83376ms)
✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (62.891626ms)
✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (30.493765ms)
✔ help or a detail box cut to fit keeps its last line, Esc included (Dewey) (15.406428ms)
✔ keys: views, moving, trail of a selection, the command line, help and quit (73.149516ms)
✔ one option: the status line says Press 1, not 1-1 (15.210703ms)
✔ no business: the app says so and :business picks one (8.619387ms)
✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (1.803719ms)
✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (164.997031ms)
✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.443614ms)
✔ decide with --note still asks; a No sends nothing (9.732223ms)
✔ the app's business comes from a live bus host only (0.979552ms)
✔ runApp leaves the terminal as it found it on q, end of input, SIGTERM, SIGINT, SIGHUP and an error, the first frame's included (6.861451ms)
✔ draw is pure: same state, same frame (0.746609ms)
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (31.010812ms)
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (23.849413ms)
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (21.145861ms)
✔ decide prints a declining choice as declining (16.669356ms)
✔ an unknown outcome is reported once and never resent (16.143936ms)
✔ a decision closed before the answer arrives exits 2 and points at its trail (16.817295ms)
✔ a prefix that matches two open decisions exits 2 and resolves neither (15.339209ms)
✔ without --business a command uses the live host's business, and a stale host.json is not a host (13.980476ms)
✔ every human command refuses inside an agent run before it touches the bus (13.442105ms)
✔ usage errors exit 4; no business and no host is a usage error (15.069535ms)
✔ agents and tasks print through the broker (13.574066ms)
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.971942ms)
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (72.814485ms)
✔ trackers come from the tracker.* variables of the one project that names a tracker project (89.258734ms)
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (42.902867ms)
✔ two projects that each name a tracker project refuse, since the boot shape holds one (46.122674ms)
✔ a business without tracker.baseUrl gets no trackers entry (46.961323ms)
✔ an unknown business and a broken system config refuse with exit 3 (65.294014ms)
✔ follow prints an unsolicited message after plain talk returned (37.572482ms)
✔ talk --follow continues after reply and prints a later unsolicited message (34.69679ms)
✔ inbox lists all unread human messages under decisions, does not deliver or read them (300.970315ms)
✔ follow refuses agent context and bad arguments (10.27396ms)
✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (9.023136ms)
✔ plain talk leaves the process SIGINT handlers unchanged (10.297506ms)
✔ empty views say so (1.20063ms)
✔ the trail keeps the broker's order and names a decision's task without its rows (1.32809ms)
✔ tasks print the tracker fields the snapshot carries (0.221093ms)
✔ the review loop: the coder pushes and asks, the reviewer fetches and approves, and the task's trail holds each step (462.894648ms)
✔ refusals: no action, non-fast-forward, an unknown commit or request, and a push for another task moves only that task's ref (335.907022ms)
✔ the size cap refuses a push before any byte (146.574784ms)
✔ a fetch over the cap refuses before any byte reaches the reviewer (355.906517ms)
✔ the socket refuses a request with no capability, an unknown one, and a bad request line (162.154682ms)
✔ a rigged clone runs nothing: hooks, fsmonitor, a filter, a gitlink and a symlinked .git/config leave no marker (265.237099ms)
✔ an identity with no role, or of another business, refuses not-a-role and moves no ref (215.575656ms)
✔ a review of a project the business no longer has refuses unknown-project (195.751869ms)
✔ past maxActive connections the socket refuses git-busy, and a hung-up connection frees its slot (127.067561ms)
✔ close() answers host-closing to a connection still sending its request line (122.81349ms)
✔ a bundle that trickles in refuses body-timeout at its deadline and frees its slot (533.669416ms)
✔ a reviewer gone before its bundle was sent is filed fetch-aborted, not as the host's fault (446.171438ms)
✔ a reviewer that hangs up the moment it has the whole bundle was served, not aborted (509.475251ms)
✔ send: a close after every byte left is served; one that fails a queued write is fetch-aborted (41.582627ms)
✔ a bundle with no bytes for bodyIdle refuses body-timeout long before its deadline (453.725854ms)
✔ send: a close once the loop is past its last chunk is served, and a failed write there is fetch-aborted (44.714286ms)
✔ letGo waits for an answer to leave for at most its cap, then destroys the socket (300.745188ms)
✔ a record that fails and can't be undone refuses rollback-failed, which the host logs (355.23866ms)
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (924.003791ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (182.905222ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (85.586283ms)
✔ two bus starts racing for one data root leave one host running; the other refuses that a host already runs (478.889989ms)
✔ a host takes over a host.lock its dead host left, and puts back a live claim that replaced it meanwhile (1.579175ms)
✔ 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.970864ms)
✔ a live host's lock stays, however old the file is (0.659135ms)
✔ a host.lock left by a host killed with SIGKILL doesn't stop the next bus start (760.448816ms)
✔ while a host boots, a reader sees no host: the human commands still ask for --business, stop and status find none (221.046742ms)
✔ a host started before host.lock existed, holding only host.json, still refuses a second start, which leaves no lock (27.428979ms)
✔ a broker fork that throws leaves no host.lock and no host.json (30.784261ms)
✔ a broker send that throws leaves no host.lock and no host.json (31.486212ms)
✔ a broker that misses its boot wait leaves no host.lock and no host.json (37.74624ms)
✔ a notifier fork that throws stops the broker and leaves no host.lock and no host.json (83.279435ms)
✔ a notifier send that throws stops the broker and leaves no host.lock and no host.json (90.942799ms)
✔ a boot reply with no reader for the business refuses with exit 3 naming it, before the notifier starts, and leaves no host.lock and no live broker (86.153244ms)
✔ 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 (80.064267ms)
✔ a host.json that can't be written stops both children and leaves no host.lock (198.679676ms)
✔ an error event on the broker before it replies fails the start at once, and leaves no host.lock and no live child (33.223042ms)
✔ an error event on the notifier before it replies fails the start at once, and leaves no host.lock and no live child (84.449929ms)
✔ an error event on the broker while the notifier starts fails the start once the notifier replies (209.469683ms)
✔ 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 (203.256422ms)
✔ 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 (195.462446ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1091.518407ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (167.344726ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (85.563382ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (147.73826ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (136.407305ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (76.875591ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (136.686127ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (87.172494ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (155.273501ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (24.019176ms)
✔ bus stop refuses to signal a live pid that is not a bus host (203.114406ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (267.40057ms)
✔ bus start refuses with exit 3 without a notifier config (119.409713ms)
✔ bus start runs until bus stop; status reports it while it runs (733.89035ms)
✔ bus-service.sh renders the unit and installs it into a given directory (64.276204ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (81.417527ms)
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (14.605023ms)
✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (9.333543ms)
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1242.257226ms)
✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (419.563448ms)
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (3539.169985ms)
✔ 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.12262ms)
✔ a runner that stops at once ends its launch with the runner's reason (191.187442ms)
✔ a Claude Code launch with no session credential refuses before it starts (98.040699ms)
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (178.633716ms)
✔ a runner killed from the host ends its launch as killed, not failed (244.156ms)
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (105.311418ms)
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1260.203462ms)
✔ 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 (626.499496ms)
✔ 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 (3548.422588ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (89.14685ms)
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (89.756313ms)
✔ a launch client that never closes its side doesn't hold the host's close (86.455323ms)
✔ a runner that ignores SIGTERM is killed when the host closes (1199.159784ms)
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (4.464462ms)
✔ zoned uses the IANA zone across DST (25.683299ms)
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (40.95855ms)
✔ two blocking decisions get two DMs with different nonces (21.854105ms)
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.312043ms)
✔ a failed DM is journaled, backs off, and is retried until it lands (19.49567ms)
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (23.848827ms)
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (23.074812ms)
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (17.076166ms)
✔ 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 (120.61351ms)
✔ a restart after the second refusal does not send before that refusal's 30 min are up (59.927136ms)
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (14.12565ms)
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (11.094881ms)
✔ an inbox read failure is logged and the next poll retries (0.747952ms)
✔ no Discord id reaches the journal or the log (9.790072ms)
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (1.934302ms)
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.61942ms)
✔ the journal: a whole file that is one torn line truncates to empty (1.264351ms)
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.57017ms)
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.702592ms)
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.003221ms)
✔ the journal: a symlinked directory refuses and says it is a link (0.342545ms)
✔ 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.603549ms)
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (2.090942ms)
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.510464ms)
✔ digest content stays within Discord's 2000 characters (0.315826ms)
✔ runLoop never overlaps ticks and stops after the one in flight (110.940236ms)
✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (529.621208ms)
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (442.829774ms)
✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (482.787297ms)
✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (285.730656ms)
✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (3360.500362ms)
✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (497.115879ms)
✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (173.893178ms)
✔ install never shows a template name on systemd 261; template drop-ins still refuse (179.721222ms)
✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (162.632738ms)
✔ inherited mosaic-.service.d override refuses without changing the unit (187.103951ms)
✔ inherited service.d override refuses without changing the unit (182.031116ms)
✔ inherited drop-in unreadable directory or file refuses before writing (158.469248ms)
✔ talk --tester sends a [mos test] message as tester from inside an agent and prints the role's reply (30.270079ms)
✔ talk --tester refuses text without the [mos test] prefix before any call (16.542949ms)
✔ the read verbs take --tester inside an agent, and the trail shows the tester as the sender (22.603548ms)
✔ decide, stop and launches take no --tester (16.225884ms)
✔ the tester transport runs tester-cli.mjs, and the tester's refusals exit 3 (0.50164ms)
✔ with no stand-in transport, --tester runs tester-cli.mjs on the data root's socket (0.958693ms)
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 (336.025062ms)
✔ the transport writes {business, verb, args} to the child and reads its JSON (59.959474ms)
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2208.096258ms)
✔ busExit and refuseInsideAgent (0.494712ms)
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (57.061086ms)
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1021.863658ms)
✔ a late reply the Console has listed still reaches the next talk (2032.93961ms)
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (13.662668ms)
✔ launches off and on go to the broker and change the business's launch state (13.761276ms)
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (12.454406ms)
ℹ tests 187
ℹ suites 0
ℹ pass 187
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 13204.628602
@@ -0,0 +1,17 @@
OK status with missing harness credential exits 3 and still lists accounts
OK status reports harness credential (read-only) + mosaic accounts
OK api key material never reaches output
OK oauth token material never reaches output
OK unparseable credential file exits 2
OK symlinked credential file exits 4
OK env-side credential names reported
OK env var values never reach output
OK accounts without an accounts dir reports none and creates nothing
OK accounts lists files and marks the active one
OK loose account perms flagged in listing
OK agent --auth with missing account file refuses (exit 4)
OK agent --auth with non-0600 account file refuses
OK agent --auth with invalid account name refuses
OK auth.sh without valid config refuses
selftest: 15 passed, 0 failed
@@ -0,0 +1,23 @@
On branch refactor
Your branch is up to date with 'origin/refactor'.
nothing to commit, working tree clean
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
OK dry-run committed nothing
OK apply: allowed change exits 0 (exit 0)
OK apply: attribution in commit subject
OK apply: target tree clean after commit
OK disallowed path refused (exit 1)
OK disallowed path: target untouched
OK syntax gate refused broken .mjs (exit 1)
OK syntax gate: target untouched
OK suite failure refused (exit 1)
OK suite failure: target reverted to clean
OK disabled policy refused (exit 2)
OK disabled policy: target untouched
OK failed run refused (exit 1)
OK failed run: target untouched
OK missing run exits 4 (exit 4)
OK invalid policy exits 2 (exit 2)
selftest: 17 passed, 0 failed
@@ -0,0 +1,26 @@
OK absent adapter defaults to pi
OK adapter mock validates (exit 0)
OK unsupported adapter exits 2 (exit 2)
OK env exports adapter
OK bootstrap creates default when absent (exit 0)
OK bootstrap wrote config file
OK bootstrap is idempotent on existing config (exit 0)
OK bootstrap did not rewrite existing config
OK validate missing config exits 3 (exit 3)
OK malformed JSON exits 2 (exit 2)
OK unsupported configVersion exits 2 (exit 2)
OK unknown top-level key exits 2 (exit 2)
OK unknown execution key exits 2 (exit 2)
OK unsupported backend exits 2 (exit 2)
OK unsupported environment exits 2 (exit 2)
OK relative dataRoot exits 2 (exit 2)
OK non-canonical dataRoot exits 2 (exit 2)
OK filesystem root dataRoot exits 2 (exit 2)
OK home directory dataRoot exits 2 (exit 2)
OK dataRoot containing config dir exits 2 (exit 2)
OK control character in provider exits 2 (exit 2)
OK symlinked config file exits 2 (exit 2)
OK env exports resolve correctly
OK failed validation modified nothing
selftest: 24 passed, 0 failed
@@ -0,0 +1,70 @@
toolchain: node v26.8.1
OK syntax: packages/discord/src/approvals.mjs
OK syntax: packages/discord/src/authorize.mjs
OK syntax: packages/discord/src/binding.mjs
OK syntax: packages/discord/src/cli.mjs
OK syntax: packages/discord/src/connector.mjs
OK syntax: packages/discord/src/context.mjs
OK syntax: packages/discord/src/engine-pi.mjs
OK syntax: packages/discord/src/errors.mjs
OK syntax: packages/discord/src/gateway.mjs
OK syntax: packages/discord/src/git.mjs
OK syntax: packages/discord/src/journal.mjs
OK syntax: packages/discord/src/notify.mjs
OK syntax: packages/discord/src/rest.mjs
OK syntax: packages/discord/src/setspark.mjs
OK syntax: packages/discord/src/tools.mjs
OK syntax: packages/discord/src/web.mjs
OK syntax: packages/discord/bin/git-credential.mjs
OK syntax: packages/discord/extension/tools.mjs
OK syntax: packages/discord/tests/approvals.test.mjs
OK syntax: packages/discord/tests/authorize.test.mjs
OK syntax: packages/discord/tests/binding.test.mjs
OK syntax: packages/discord/tests/connector.test.mjs
OK syntax: packages/discord/tests/context.test.mjs
OK syntax: packages/discord/tests/engine.test.mjs
OK syntax: packages/discord/tests/fake-pi.mjs
OK syntax: packages/discord/tests/gateway.test.mjs
OK syntax: packages/discord/tests/git.test.mjs
OK syntax: packages/discord/tests/helpers.mjs
OK syntax: packages/discord/tests/journal.test.mjs
OK syntax: packages/discord/tests/notify.test.mjs
OK syntax: packages/discord/tests/recover.test.mjs
OK syntax: packages/discord/tests/rest.test.mjs
OK syntax: packages/discord/tests/setspark.test.mjs
OK syntax: packages/discord/tests/tools.test.mjs
OK syntax: packages/discord/tests/web.test.mjs
OK syntax: packages/discord/fixtures/claim-worker.mjs
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
OK syntax: scripts/discord.sh
OK syntax: scripts/discord-service.sh
OK packages/discord declares no dependencies
OK no bot-token-shaped string in packages/discord
OK fixture binding uses placeholder ids only
OK fixture binding validates
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
OK real pi with protocol vault adds reserve_id to the git verbs
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
OK real pi refuses a git key on a read-only root (fail closed)
OK real pi with the pilot flags (--no-tools) exposes no tool at all
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test packages/discord/tests/ (ℹ pass 178)
OK scripts/discord.sh --help exits 0
OK scripts/discord.sh check without a binding exits 4
OK scripts/discord.sh recover without a binding exits 4
OK scripts/discord.sh reload without a binding exits 4
OK scripts/discord-service.sh without a command exits 4
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
OK service install writes the rendered unit (0644) and leaves no temp file
OK service install a second time reports unchanged
OK systemd-analyze verify accepts the rendered unit
OK service uninstall removes the unit file
OK service install with an unknown flag exits 4
OK service install with USER unset finishes and names the account for lingering
discord suite: 66 passed, 0 failed
@@ -0,0 +1,21 @@
OK initial ordinary-file install
OK installed tree matches canonical source
OK installed tree has no symlinks
OK check detects installation drift
OK sync refuses to overwrite installation drift
OK check detects an extra destination file
OK check detects an extra destination directory
OK check rejects a destination symlink
OK sync accepts a canonical source update
OK updated installation matches canonical source
/mnt/storage/src/mosaic-stack-rocko-108/scripts/test-extension-package.sh: line 14: 1554475 Killed "$@" > /dev/null 2>&1
OK forced interruption kills the replacing process
OK next invocation recovers old consistent installation
OK interrupted replacement rolled back
OK sync succeeds after interruption recovery
OK unlocked stale lock file does not block
OK active lock refuses a concurrent sync
OK source symlink fails closed
OK nested second entrypoint fails closed
extension package selftest: 18 passed, 0 failed
@@ -0,0 +1,53 @@
toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0
OK syntax: scripts/foundation-inspect.mjs
OK syntax: scripts/foundation/strict-json.mjs
OK syntax: scripts/foundation/canonical.mjs
OK syntax: scripts/foundation/resolve.mjs
OK syntax: scripts/foundation/validate-record.mjs
OK syntax: scripts/foundation/fixtures/build-fixtures.mjs
OK syntax: scripts/foundation/canonical.test.mjs
OK syntax: scripts/foundation/cli.test.mjs
OK syntax: scripts/foundation/fixtures.test.mjs
OK syntax: scripts/foundation/resolve.test.mjs
OK syntax: scripts/foundation/strict-json.test.mjs
OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)
OK fixture generator runs
OK checked-in fixtures/bundles equal a fresh generation
OK checked-in fixtures/raw equal a fresh generation
OK checked-in fixtures/index.json equal a fresh generation
OK checked-in demo bundles equal a fresh generation
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test scripts/foundation/ (ℹ pass 80)
OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)
platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999)
node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases)
schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
profile column (schema-valid records only): profile-valid 510, profile-invalid 30
profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns
OK oracle: zero schema-column disagreements with the pinned checker
OK oracle: strict-only profile refusals are counted and asserted
OK demo: permitted read preview exits 0 (exit 0)
OK demo: permitted file.change preview exits 0 (exit 0)
OK demo: assignment.change proposal is unresolved (exit 3) (exit 3)
OK demo: revoked registration is refused (exit 3) (exit 3)
OK demo: message is not authority (exit 3) (exit 3)
OK usage: no arguments exits 2 (exit 2)
OK io: missing file exits 4 (exit 4)
OK io: directory exits 4 (exit 4)
OK io: symlink exits 4 (O_NOFOLLOW) (exit 4)
OK bound: oversize fixture exits 2 (exit 2)
OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2)
OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2)
OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0)
OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed
OK text output starts with the disclaimer
OK json output is valid JSON with result allowed and exactly the charter §7 fields
OK json golden matches byte-for-byte
OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal
OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs
OK canary never printed (bundle run and credential-file run)
OK a non-bundle JSON file is refused at the shape gate, not read into output
OK no field of the non-bundle file is echoed
selftest: 44 passed, 0 failed
@@ -0,0 +1,86 @@
OK args: exit 2, target-branch
OK args: exit 2, target-branch
OK args: exit 4
OK args: exit 4
OK args: exit 4
OK --base next refuses before the config is read (got 2)
OK a missing system config exits 3 (got 3)
OK nobus: exit 3
OK schema-table: exit 2, bus-schema
OK schema-meta: exit 2, bus-schema
OK openBus opens the database read-only
OK other-business: exit 2, decision-not-found
OK no-raise: exit 2, decision-not-found
OK not-a-merge: exit 2, not-a-merge
OK open: exit 2, not-resolved
OK withdrawn: exit 2, not-resolved
OK by-agent: exit 2, not-human
OK via-webui: exit 2, not-human
OK no-answer: exit 2, not-human
OK answer-role: exit 2, not-human
OK answer-run: exit 2, not-human
OK answer-choice: exit 2, not-human
OK two-answers: exit 2, not-human
OK chose-no: exit 2, not-merge-option
OK two-refs: exit 2, work-ref
OK bad-ref: exit 2, work-ref
OK task-ref-disagrees: exit 2, work-ref
OK raise-task-disagrees: exit 2, work-ref
OK target-main: exit 2, target-branch
OK superseded-question: exit 2, superseded
OK superseded-task: exit 2, superseded
OK superseded-by: exit 2, superseded
OK no-verdict: exit 2, no-verdict
OK changes-later: exit 2, not-approved
OK short-commit: exit 2, not-approved
OK wrong-task-body: exit 2, not-approved
OK args: exit 2, decision-not-found
OK earlier-merge: exit 0
OK ref-moved: exit 2, ref-moved
OK root-top: exit 2, root-file
OK root-newdir: exit 2, root-file
OK root-src: exit 2, root-file
OK unrelated: exit 2, base-conflict
OK empty: exit 2, base-conflict
OK an empty reviewed diff says so
OK dirty-changed: exit 2, checkout-dirty
OK dirty-staged: exit 2, checkout-dirty
OK dirty-untracked: exit 2, checkout-dirty
OK conflict: exit 2, base-conflict
OK a refusal after the worktree is added leaves no worktree or scratch
OK cleanup looks for the worktree's Docker network
OK dirty-during: exit 2, checkout-dirty
OK a reviewed path dirtied during the gate moves no ref
OK drift: exit 2, patch-drift
OK patch-drift leaves no worktree or scratch
OK gate-pkg: exit 2, gate-failed
OK gate-failed names the failing step's log, and it exists
OK gate-failed leaves no worktree or scratch
OK gate-script: exit 2, gate-failed
OK a failing scripts/test-*.sh fails the gate
OK a failed gate moves no ref
OK nomods: exit 4
OK a target without node_modules leaves no worktree or scratch
OK moved: exit 2, base-moved
OK base-moved keeps the base where it moved to
OK dry: exit 0
OK --dry-run writes no ref
OK --dry-run prints the commit with --subject and --attribution
OK --dry-run runs the gate
OK --dry-run leaves no worktree or scratch
OK land: exit 0
OK the landing is one commit on the old base
OK its diff is the reviewed diff by patch-id
OK its tree is the dry run's
OK the message has the option's subject, the citations and the attribution last
OK it prints the line for the PM
OK the checked-out base has the reviewed paths and keeps the unrelated edit
OK only refs/heads/refactor exists in the target
OK the work ref is unchanged
OK the bus database is unchanged
OK the gate ran each package suite but queue, and each scripts/test-*.sh
OK the landing leaves no worktree or scratch
OK the target's node_modules is still there, with its contents
OK the worktree's Docker network is removed
---
land-work selftests: 84 passed, 0 failed
@@ -0,0 +1,36 @@
toolchain: node v26.8.1, git version 2.55.0
OK syntax: packages/queue/src/cli.mjs
OK syntax: packages/queue/src/errors.mjs
OK syntax: packages/queue/src/io.mjs
OK syntax: packages/queue/src/lock.mjs
OK syntax: packages/queue/src/queue.mjs
OK syntax: packages/queue/src/review.mjs
OK syntax: packages/queue/src/store.mjs
OK syntax: packages/queue/tests/commit.test.mjs
OK syntax: packages/queue/tests/data.test.mjs
OK syntax: packages/queue/tests/dispatch.test.mjs
OK syntax: packages/queue/tests/gate-owner.test.mjs
OK syntax: packages/queue/tests/helpers.mjs
OK syntax: packages/queue/tests/lock.test.mjs
OK syntax: packages/queue/tests/migration.test.mjs
OK syntax: packages/queue/tests/review.test.mjs
OK syntax: packages/queue/tests/store.test.mjs
OK syntax: packages/queue/tests/write.test.mjs
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
OK syntax: scripts/queue-commit.sh
OK syntax: scripts/git-hooks/pre-commit
OK syntax: scripts/mosaic
OK queue-commit.sh, the guard and scripts/mosaic are executable
OK packages/queue declares no dependencies
ℹ tests 156
ℹ pass 156
ℹ fail 0
OK node --test packages/queue/tests/
OK scripts/mosaic queue help
skip queue verify and render --check: this checkout (/mnt/storage/src/mosaic-stack-rocko-108) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
queue suite: 28 passed, 0 failed
@@ -0,0 +1,7 @@
OK valid RELEASE resolves (exit 0)
OK invalid RELEASE exits 1 (exit 1)
OK missing RELEASE exits 1 (exit 1)
OK valid RELEASE leaves image tag consistent with version
skip state-machine cases (docker daemon unavailable)
selftest: 4 passed, 0 failed
@@ -0,0 +1,32 @@
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)
skip adapter seam cases (docker daemon unavailable)
skip workspace/capability cases (docker daemon unavailable)
skip live task cases (docker unavailable)
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
skip live recall pair: user recall run, recalled user name (docker daemon unavailable)
OK no agent identity on headless run
selftest: 26 passed, 0 failed
@@ -0,0 +1,9 @@
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (384.189666ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 433.222778
@@ -0,0 +1,12 @@
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (519.268069ms)
✔ inherited mosaic-.service.d override refuses without changing the unit (157.551223ms)
✔ inherited service.d override refuses without changing the unit (155.66251ms)
✔ inherited drop-in unreadable directory or file refuses before writing (135.472565ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1017.932833
@@ -0,0 +1,32 @@
{
"@google/genai": {
"version": "1.52.0",
"hasInstallScript": true,
"lifecycle": {
"preinstall": "echo 'preinstall: no-op'"
},
"installedPackageJsonSha256": "ec761756421ea5502c23dbebfb4bc2b74c3ff842597199f2f330afc49cbdedc7",
"externalBundleImports": [],
"copiedSourceMentions": []
},
"esbuild": {
"version": "0.28.1",
"hasInstallScript": true,
"lifecycle": {
"postinstall": "node install.js"
},
"installedPackageJsonSha256": "d55d1d19fcc5b6079e4a71dd4111340c79c682bc36835ac7058a0c364c7db58a",
"externalBundleImports": [],
"copiedSourceMentions": []
},
"protobufjs": {
"version": "7.6.6",
"hasInstallScript": true,
"lifecycle": {
"postinstall": "node scripts/postinstall"
},
"installedPackageJsonSha256": "0e56b70de03bffc143e74311b42ae22b557370a4a1f4b5066568fbc77857639a",
"externalBundleImports": [],
"copiedSourceMentions": []
}
}
@@ -0,0 +1,9 @@
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (371.608739ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 423.313405
@@ -0,0 +1,30 @@
✖ inherited drop-in unreadable directory or file refuses before writing (176.202383ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 226.195254
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:206:1
✖ inherited drop-in unreadable directory or file refuses before writing (176.202383ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:209:83)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,30 @@
✖ inherited drop-in unreadable directory or file refuses before writing (153.010377ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 207.505872
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:206:1
✖ inherited drop-in unreadable directory or file refuses before writing (153.010377ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:211:86)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,30 @@
✖ inherited service.d override refuses without changing the unit (149.469589ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 204.588195
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:193:53
✖ inherited service.d override refuses without changing the unit (149.469589ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:201:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,30 @@
✖ inherited mosaic-.service.d override refuses without changing the unit (157.223428ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 207.691119
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:193:53
✖ inherited mosaic-.service.d override refuses without changing the unit (157.223428ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:201:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,32 @@
✖ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (332.205475ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 382.931859
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:43:1
✖ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (332.205475ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ '# CLI copy unspecified: /mnt/storage/scratch/tmp/stack-copy-UZgQWO/copies/9a8600610d3b67a76bc0831530b76bcef2184c55'
- '# CLI runs from pinned copy: /mnt/storage/scratch/tmp/stack-copy-UZgQWO/copies/9a8600610d3b67a76bc0831530b76bcef2184c55'
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:51:9)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: '# CLI copy unspecified: /mnt/storage/scratch/tmp/stack-copy-UZgQWO/copies/9a8600610d3b67a76bc0831530b76bcef2184c55',
expected: '# CLI runs from pinned copy: /mnt/storage/scratch/tmp/stack-copy-UZgQWO/copies/9a8600610d3b67a76bc0831530b76bcef2184c55',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,32 @@
✖ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (331.301415ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 386.556311
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:43:1
✖ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (331.301415ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ '# Lease explanation omitted.'
- '# The Q81 lease probe still answers at mosaic-old.'
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:52:9)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: '# Lease explanation omitted.',
expected: '# The Q81 lease probe still answers at mosaic-old.',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,37 @@
[
{
"name": "miss-prefix",
"exit": 1,
"status": "killed"
},
{
"name": "miss-all-services",
"exit": 1,
"status": "killed"
},
{
"name": "ignore-unreadable-directory",
"exit": 1,
"status": "killed"
},
{
"name": "ignore-unreadable-file",
"exit": 1,
"status": "killed"
},
{
"name": "omit-copy-comment",
"exit": 1,
"status": "killed"
},
{
"name": "omit-lease-comment",
"exit": 1,
"status": "killed"
},
{
"name": "accept-foreign-check-wrapper",
"exit": 0,
"status": "survived"
}
]
@@ -0,0 +1,30 @@
✖ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (370.564265ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 423.590927
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:43:1
✖ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (370.564265ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:65:9)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,30 @@
✖ inherited drop-in unreadable directory or file refuses before writing (149.650683ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 201.468523
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:206:1
✖ inherited drop-in unreadable directory or file refuses before writing (149.650683ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:209:83)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,30 @@
✖ inherited drop-in unreadable directory or file refuses before writing (157.344125ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 206.277407
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:206:1
✖ inherited drop-in unreadable directory or file refuses before writing (157.344125ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:211:86)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,30 @@
✖ inherited service.d override refuses without changing the unit (147.698195ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 202.087926
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:193:53
✖ inherited service.d override refuses without changing the unit (147.698195ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:201:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,30 @@
✖ inherited mosaic-.service.d override refuses without changing the unit (150.701816ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 200.884936
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:193:53
✖ inherited mosaic-.service.d override refuses without changing the unit (150.701816ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:201:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,32 @@
✖ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (329.834503ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 386.342447
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:43:1
✖ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (329.834503ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ '# CLI copy unspecified: /mnt/storage/scratch/tmp/stack-copy-14DZ3i/copies/68b0dcbc6b74877aa3d7f4273f25c60c82a2fed0'
- '# CLI runs from pinned copy: /mnt/storage/scratch/tmp/stack-copy-14DZ3i/copies/68b0dcbc6b74877aa3d7f4273f25c60c82a2fed0'
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:51:9)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: '# CLI copy unspecified: /mnt/storage/scratch/tmp/stack-copy-14DZ3i/copies/68b0dcbc6b74877aa3d7f4273f25c60c82a2fed0',
expected: '# CLI runs from pinned copy: /mnt/storage/scratch/tmp/stack-copy-14DZ3i/copies/68b0dcbc6b74877aa3d7f4273f25c60c82a2fed0',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,32 @@
✖ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (336.722572ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 387.262429
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:43:1
✖ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (336.722572ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ '# Lease explanation omitted.'
- '# The Q81 lease probe still answers at mosaic-old.'
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:52:9)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: '# Lease explanation omitted.',
expected: '# The Q81 lease probe still answers at mosaic-old.',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,37 @@
[
{
"name": "miss-prefix",
"exit": 1,
"status": "killed"
},
{
"name": "miss-all-services",
"exit": 1,
"status": "killed"
},
{
"name": "ignore-unreadable-directory",
"exit": 1,
"status": "killed"
},
{
"name": "ignore-unreadable-file",
"exit": 1,
"status": "killed"
},
{
"name": "omit-copy-comment",
"exit": 1,
"status": "killed"
},
{
"name": "omit-lease-comment",
"exit": 1,
"status": "killed"
},
{
"name": "accept-foreign-check-wrapper",
"exit": 1,
"status": "killed"
}
]
@@ -0,0 +1 @@
Named network absent or Docker unavailable; gate used a nonexistent socket and created no Docker network.
@@ -0,0 +1,2 @@
[]
Error response from daemon: network mosaic-stack-rocko-108_default not found
@@ -0,0 +1 @@
busy: []
+100
View File
@@ -0,0 +1,100 @@
✖ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (338.641838ms)
✖ inherited mosaic-.service.d override refuses without changing the unit (163.843449ms)
✖ inherited service.d override refuses without changing the unit (160.748391ms)
✖ inherited drop-in unreadable directory or file refuses before writing (156.421459ms)
ℹ tests 4
ℹ suites 0
ℹ pass 0
ℹ fail 4
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 874.645626
✖ failing tests:
test at packages/cli/tests/stack-copy.test.mjs:43:1
✖ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (338.641838ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ '[ "${1:-}" = __lease-capability ] && exec "/mnt/storage/scratch/tmp/stack-copy-CqFcXA/bin/mosaic-old" "$@"'
- '# CLI runs from pinned copy: /mnt/storage/scratch/tmp/stack-copy-CqFcXA/copies/532780c22bd934403daeb69912a99e3818815e56'
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:51:9)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: '[ "${1:-}" = __lease-capability ] && exec "/mnt/storage/scratch/tmp/stack-copy-CqFcXA/bin/mosaic-old" "$@"',
expected: '# CLI runs from pinned copy: /mnt/storage/scratch/tmp/stack-copy-CqFcXA/copies/532780c22bd934403daeb69912a99e3818815e56',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/stack-copy.test.mjs:191:53
✖ inherited mosaic-.service.d override refuses without changing the unit (163.843449ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:199: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 node:internal/test_runner/test:1305:31
at node:internal/process/task_queues:151:7
at AsyncResource.runInAsyncScope (node:async_hooks:226:14)
at AsyncResource.runMicrotask (node:internal/process/task_queues:148:8) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/stack-copy.test.mjs:191:53
✖ inherited service.d override refuses without changing the unit (160.748391ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:199:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/stack-copy.test.mjs:204:1
✖ inherited drop-in unreadable directory or file refuses before writing (156.421459ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/src/mosaic-stack-rocko-108/packages/cli/tests/stack-copy.test.mjs:207:83)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,127 @@
[
{
"suite": "node-cli",
"exit": 0,
"began": "2026-10-14T20:08:16.669752+00:00",
"ended": "2026-10-14T20:08:29.916793+00:00",
"summaries": [
"\u2714 a runner killed from the host ends its launch as killed, not failed (244.156ms)",
"\u2714 a failed DM is journaled, backs off, and is retried until it lands (19.49567ms)",
"\u2714 bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (482.787297ms)",
"\u2139 tests 187",
"\u2139 pass 187",
"\u2139 fail 0",
"\u2139 cancelled 0",
"\u2139 skipped 0"
]
},
{
"suite": "test-auth",
"exit": 0,
"began": "2026-10-14T20:08:29.917023+00:00",
"ended": "2026-10-14T20:08:30.532205+00:00",
"summaries": [
"selftest: 15 passed, 0 failed"
]
},
{
"suite": "test-conductor",
"exit": 0,
"began": "2026-10-14T20:08:30.532452+00:00",
"ended": "2026-10-14T20:08:35.257973+00:00",
"summaries": [
"OK failed run refused (exit 1)",
"OK failed run: target untouched",
"selftest: 17 passed, 0 failed"
]
},
{
"suite": "test-config",
"exit": 0,
"began": "2026-10-14T20:08:35.258217+00:00",
"ended": "2026-10-14T20:08:36.927811+00:00",
"summaries": [
"OK failed validation modified nothing",
"selftest: 24 passed, 0 failed"
]
},
{
"suite": "test-discord",
"exit": 0,
"began": "2026-10-14T20:08:36.928073+00:00",
"ended": "2026-10-14T20:08:43.506603+00:00",
"summaries": [
"OK node --test packages/discord/tests/ (\u2139 pass 178)",
"discord suite: 66 passed, 0 failed"
]
},
{
"suite": "test-extension-package",
"exit": 0,
"began": "2026-10-14T20:08:43.506874+00:00",
"ended": "2026-10-14T20:08:44.823470+00:00",
"summaries": [
"extension package selftest: 18 passed, 0 failed"
]
},
{
"suite": "test-foundation",
"exit": 0,
"began": "2026-10-14T20:08:44.823736+00:00",
"ended": "2026-10-14T20:09:21.468582+00:00",
"summaries": [
"OK node --test scripts/foundation/ (\u2139 pass 80)",
"selftest: 44 passed, 0 failed"
]
},
{
"suite": "test-land-work",
"exit": 0,
"began": "2026-10-14T20:09:21.468850+00:00",
"ended": "2026-10-14T20:09:29.950720+00:00",
"summaries": [
"OK gate-pkg: exit 2, gate-failed",
"OK gate-failed names the failing step's log, and it exists",
"OK gate-failed leaves no worktree or scratch",
"OK gate-script: exit 2, gate-failed",
"OK a failed gate moves no ref",
"land-work selftests: 84 passed, 0 failed"
]
},
{
"suite": "test-queue",
"exit": 0,
"began": "2026-10-14T20:09:29.951035+00:00",
"ended": "2026-10-14T20:09:50.306428+00:00",
"summaries": [
"\u2139 tests 156",
"\u2139 pass 156",
"\u2139 fail 0",
"skip queue verify and render --check: this checkout (/mnt/storage/src/mosaic-stack-rocko-108) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)",
"queue suite: 28 passed, 0 failed"
]
},
{
"suite": "test-release",
"exit": 0,
"began": "2026-10-14T20:09:50.306714+00:00",
"ended": "2026-10-14T20:09:50.420924+00:00",
"summaries": [
"skip state-machine cases (docker daemon unavailable)",
"selftest: 4 passed, 0 failed"
]
},
{
"suite": "test-task",
"exit": 0,
"began": "2026-10-14T20:09:50.421163+00:00",
"ended": "2026-10-14T20:09:52.390220+00:00",
"summaries": [
"skip adapter seam cases (docker daemon unavailable)",
"skip workspace/capability cases (docker daemon unavailable)",
"skip live task cases (docker unavailable)",
"skip live recall pair: user recall run, recalled user name (docker daemon unavailable)",
"selftest: 26 passed, 0 failed"
]
}
]
@@ -0,0 +1,59 @@
5e8e8555b75b9f8cde109bf4d4618ed32635502c7e4c0a803a1ff3839f8df07c BUILD.md
585c050b0dbb1141063cb8b0c36781a33f78362e97be677750cac5858156de87 audit-install-scripts.py
003cabffa810b8cc58b269e7ddb2049ac1016afaa3763118e500b0d0417bc19b base.txt
e8c49c1f8781520248a1d081ad33d5b9bfb5ee100c8bd457fcbf0a2436a8bf17 build.patch
19ef7133db592787c24fa473357687a5741ccd7823627b970624c930a7611dbc candidate-manifest.sha256
a25433983b7945d18aa0717d9323a9593c50d3ea95fa73c886583e06e75d48c9 files.txt
1eb6d322c6864f21d2194b257a7d7f97e0a55761bf476b80cf3e3685b40fce94 gate.py
93c47f6773497c180b6afc5479bcd0a4e1b57957400d785793a55027413c8cc8 mutants.py
ffa47b17a6b98aadf890737dea27518afb3726ae0f09193ec70ee548c2ebe3af out/final-verification.txt
64a7b7656cac69dad18383be6eb04192244bc9b53132316cd3c4a495216fe27c out/full/gate-results.json
18a032eb2cab4739954ea172a4a10310c3d5a3f76c8a568100fe44289a932deb out/full/node-cli.txt
f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 out/full/test-auth.txt
e1206366f07f3d9029530dcdcb55c0282d3f052adc60fc7fd51642d5d4ec3600 out/full/test-conductor.txt
52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 out/full/test-config.txt
7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe out/full/test-discord.txt
03501c913499fa5b3c3b9e71d5d60a8a51f71d3336f3cb791ab27e46dac8c15a out/full/test-extension-package.txt
83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f out/full/test-foundation.txt
8e2254aa3e1bb9b205ab72c68273d447c0328c9f156ccba2a46f3a0476468b58 out/full/test-land-work.txt
37bb16fe964cf2bfa6b6be1a34a0ca92a87b9b51a9e6be047dcd4d9950bb96f7 out/full/test-queue.txt
6183e6b9b05edb497e92ffd09a19bba49c6f656c750ecb2acc283021113d1a98 out/full/test-release.txt
5dc16a91c36d6ff437e5fbb66764ece758e412f32f74b5a6d0c9cb9cad332f59 out/full/test-task.txt
f81b1dbf039fcb9980eab6b5812c28fff5c80bfb37c13ce738682bc03d783ccd out/full-first/gate-results.json
edf055e251ecde70ba9651cc759dd2438f02c5dba8b80ea252c1c98a73b9262a out/full-first/node-cli.txt
f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 out/full-first/test-auth.txt
e1206366f07f3d9029530dcdcb55c0282d3f052adc60fc7fd51642d5d4ec3600 out/full-first/test-conductor.txt
52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 out/full-first/test-config.txt
7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe out/full-first/test-discord.txt
61c4e82851afe59369ffb17bdc55e1a077080b8aa81681cc0b0811d445918fb2 out/full-first/test-extension-package.txt
83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f out/full-first/test-foundation.txt
8e2254aa3e1bb9b205ab72c68273d447c0328c9f156ccba2a46f3a0476468b58 out/full-first/test-land-work.txt
37bb16fe964cf2bfa6b6be1a34a0ca92a87b9b51a9e6be047dcd4d9950bb96f7 out/full-first/test-queue.txt
6183e6b9b05edb497e92ffd09a19bba49c6f656c750ecb2acc283021113d1a98 out/full-first/test-release.txt
5dc16a91c36d6ff437e5fbb66764ece758e412f32f74b5a6d0c9cb9cad332f59 out/full-first/test-task.txt
d151c8be4ba3e351a770c5853a54b197ef08dca1f662cddc4c0acad6e1eeaa52 out/green-isolated-marker.txt
42d55b5184fbc5ed95b8d4ff0bf7a3802839a60023308cfabbfc5825c9fef02f out/green.txt
c146253914903ad51ba9884201fa32d4606d7aceb49f48cfd084744139915e9d out/install-scripts-audit.json
5e07879b690ae4eeaa7df4856a15f9b7cead4cd782c637bc48556c264e09301a out/mutants/accept-foreign-check-wrapper.txt
4a9123a9aeb702659a7fe7037008aa6c8b6f1aeaa2ee3f03e3b5e620e1501a50 out/mutants/ignore-unreadable-directory.txt
326b0c2744b5865d722c4efdd14ef68c5a5a6d5f5b16d248b90d0ada39d90150 out/mutants/ignore-unreadable-file.txt
3e8174e924ab6b6bff1d706ac41b8a918f3e2ace417ef9e1cbedec6755851671 out/mutants/miss-all-services.txt
93724848a18905a0270d6239953525d66f60ad71f3804401008f8c1ed8460355 out/mutants/miss-prefix.txt
dee162b11afd058f1501eb7d957be1b6f5884528824651c187a6e6757b844275 out/mutants/omit-copy-comment.txt
a008d047f2b2e69efd1d8339d2b4c240a4ee5bbb25efe3bd424ac510e7e0a507 out/mutants/omit-lease-comment.txt
84d231d180685822733f326e4453ef0ca7ce40c16af6809f487bfbbbc5f6eefd out/mutants/summary.json
7fbbbe1b7cbc8af9df31b8bcb2c75a43822f3cbfa4e53ed32fb81b31fbdbab39 out/mutants-first/accept-foreign-check-wrapper.txt
d7901da7c6c4f23944c5a34633acc95a5c64d98534be69e34d5bad1bafd18f93 out/mutants-first/ignore-unreadable-directory.txt
554ca87470e161b579dc3590c670ef7493167d2a386a5edb91a3c6ed0d30c280 out/mutants-first/ignore-unreadable-file.txt
a2a460f462f1091350b9fd6f1bdd543921cf56649534d12520f07382e3a917b6 out/mutants-first/miss-all-services.txt
0f6e498da07b1e8b38cfdc6f421f26fe73dfca259b8a1fb31f36e56d281422c5 out/mutants-first/miss-prefix.txt
a626b75eaefae7368d7fe2a10611f07f22a0cebfdf6af5cf5df85fa0b1403856 out/mutants-first/omit-copy-comment.txt
bed2e0fa233e895c0a1ec23f1b5820f47e3bf45959e0c0e4dceb50107d1abcf8 out/mutants-first/omit-lease-comment.txt
ad3e9e23c2239a7e894a236624e23b7091b9c509eaf748ea629aa23f852488ee out/mutants-first/summary.json
e9708e31600a4fedf102465e4bdf32a58ca944912088a74cad48819fd140d6ae out/network-clear.txt
2b0654a7fadd83483005123095dcd7eb27cc88ec06fab628d937c28a42ba74de out/network-inspect.txt
7b1c894bf5ac4017fc38d5d07b725f71fa4f0edd11ff68028ed53b8e6a2263a9 out/process-clear.txt
f54aab59de13c531c6ab7fab0a2ac4883c0f6445301871f5f7d2b801203c5a8c out/red.txt
2bcaa72f44c6698ec3416b2d65b1fd1c279eff0426a4f5ad97e60897e3fbbb5d out/suite-counts.json
6c5dc14fb2f79c16a3910d8e5161f9bfbfbc6826e0c82be0de8b9bb9ed67c0cc process-sweep.py
ef911b81f33b3ba1a721047248503ae12c17c44601d371a28acaa2eba81a965c verify.sh
@@ -0,0 +1,13 @@
"""Inspect only processes under this row's worktree; no unrelated argv/env."""
from pathlib import Path
import os,sys
root='/mnt/storage/src/mosaic-stack-rocko-108';busy=[]
for p in Path('/proc').iterdir():
if not p.name.isdigit():continue
try:
cwd=os.readlink(p/'cwd')
if cwd==root or cwd.startswith(root+'/'):
busy.append((int(p.name),(p/'comm').read_text().strip(),cwd))
except OSError:pass
print('busy:',busy)
sys.exit(bool(busy))
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
packet=/mnt/storage/src/mosaic-stack/agents/rocko/work/queue-108/r1
[ -z "$(git branch --show-current)" ] || { echo 'detached worktree required' >&2;exit 2; }
export DOCKER_HOST=unix:///nonexistent.sock
sha256sum -c "$packet/candidate-manifest.sha256"
git diff --check
python "$packet/gate.py" "${1:?pass absolute gate output directory}"
python "$packet/mutants.py" "${2:?pass separate absolute mutant output directory}"
sha256sum -c "$packet/candidate-manifest.sha256"