Merge row 334 (#1832): Case 2 proxy unit in case2-unit.sh and case2-install.sh

This commit is contained in:
jason.woltje committed 2026-11-02 12:50:15 -06:00
commit 2b3e3ec83b
26 files changed
+2471 -25

No files matched your search

+5
View File
@@ -5840,3 +5840,8 @@ Q284 / #470 source-only security fix 2026-10-29T03:33:00.640624+00:00: pin worke
2026-10-29T05:49:53Z | Codex Daybreak Blue xhigh implementation sub-agent | CORRECTION and Mos2255 #385 review repair R2 | The 03:27:31Z dependency/no-commit statements are historical: exact pinned `[email protected]` is now materialized through the ignored `node_modules` fixture with no install, and original preparation is committed at `9936a1be1a37fdb30ba53cf2f14b7897225c3e4a`. On that unchanged HEAD, uncommitted R2 source/tests/docs make Case 2 library preview read-only until #412, drop inherited non-SSO `--send` during release planning, and refuse duplicate service `--send`. Focused RED was 0/2, 0/1, 0/1; focused GREEN and selected T53/T55/T57/T58 fixture blocks pass. No root/native/live/full-suite/CI action, commit, push, PR/API, merge or frozen/live-pin change.
2026-11-02T18:03:53Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R3 on ab2bc23c7 | R2-F1: `scripts/stack-upgrade.sh` `read_args` now scans the serve argv after `--` and refuses `args-unreadable` before anything stops when the profile is library (no `--profile`, or the last one says library, as `case2-unit.sh` reads it) and `--send` is present; the message names the remediation, and the args file is never rewritten. `do_upgrade` and `do_rollback` both call `read_args` first, so rollback is covered; factory and tester paths are unchanged. RED on ab2bc23c7: new stack-upgrade case 0/1 (exit 2 rollback-failed, A4 and render-old failed, both units stopped), controls 1/1; GREEN 2/2. R2-I1: one component case runs the real `sso_for_target` then `send_for_target` on a legacy preview `--send` pointer, carried after `--sso` and before `--tester-socket`. R2-I2: TOOLS.md says a same-copy plain activate on a legacy pointer refuses already-active and needs `--no-send`. R2-I3: no code change; `uninstall`/`status` with `--send --send` exit 1, not 4, fail-closed either way. host-run-sheet section 7 gains the migration step. Whole files, one at a time: stack-upgrade 23/23 (69 s), stack-release 225/225 (608 s; a first run was cut by my 600 s background limit at 221 passing, its orphan fixture holder killed), case2-unit 48/48 (73 s). No root, systemd, container, network, push, PR or live action.
2026-11-02T18:19:04Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R4 on d0ddfa8de | R3-F1 fixed fail-closed, no migration: `scripts/stack-upgrade.sh` adds `legacy_console`, called right after `read_args` in `do_upgrade` and `do_rollback`. For the library profile (same default and last-wins rule as R3), an installed Console unit under the driver's UNIT_DIR whose ExecStart has a `--send` token after `serve` refuses new code `console-unit-legacy` (exit 3, nothing stopped). A missing unit is left to `read_old` (`units-unreadable`). The R3 args refusal stays. `case2-unit.sh`, factory, tester, stack-release.sh and OIDC/#412 untouched. Tests in stack-upgrade.test.mjs run the real base renderer (`git show 09a847eda` of case2-unit.sh and both templates) in the module fixture with fake getent/systemctl to make the legacy unit, prove the current case2-unit.sh refuses it, then drive upgrade and rollback with the real current renderer as A4's RENDER. Controls: read-only library unit, factory host, missing Console unit. RED on d0ddfa8de code 24/25 (the main case exits 2 rollback-failed, both units stopped), GREEN 25/25 (70.1 s). host-run-sheet section 7 now gives a read-only grep of the unit and says stop and report when it carries `--send`. TOOLS.md lists the code. No hash pins on changed files. stack-release.test.mjs and case2-unit.test.mjs read no changed file and were not rerun. One local commit, no push.
2026-10-25 — Rockerduck row334 (#1832), before: Case 2 renders bus/Console only. Plan: reuse P1 proxy template/runner under --proxy, enforce three-unit re-install checks and proxy→bus→Console starts, cover refusals and both named mutants. Tests held for Sage CLEAR; design in agents/rockerduck/work/queue-334/BUILD.md.
2026-10-25 — Rockerduck row334 (#1832), RED evidence: whole case2-unit.test.mjs against baseline has 48 pass/33 expected new fail; whole case2-install.test.mjs has 27 pass/1 expected new fail. All 74 older tests pass. Implementations restored. Sage sage-4390 authorizes seat-branch candidate commit/push before GREEN; GREEN/mutants held until separate CLEAR after row331. Per-failure evidence: agents/rockerduck/work/queue-334/red-results.txt.
2026-10-26 — Rockerduck row334 Mos1961: integrated old candidate onto 09a847eda using documented rebase after diagnosing merge queue-guard refusal; old RED preserved. Whole case2-unit 81/81 and case2-install 28/28 pass; named After and direct-ExecStart mutants killed, restored tests 2/2 pass; all nice19, private temp roots, zero skips. No source edits after integration beyond temporary restored mutants, no push/live action. Source c4bba5d2a; evidence agents/rockerduck/work/queue-334/BUILD.md. Full landing gate belongs to Sage; live behavior UNVERIFIED.
+163
View File
@@ -0,0 +1,163 @@
# Row 334 — Case 2 proxy unit (#1832)
## Design at 78b69e1e6 (2026-10-25)
Scope: mos-1616 ruling 1, P1 item 7 only. No template, runner, PIN,
user-unit path, config/secret access, or live unit changes.
Current references: case2-unit.sh template seams 44-45, defaults 54-57,
render 435, check_unit 489, render checks 548-551, re-install 553-573,
active-state capture 576-580, writes/start/report 583-610.
case2-install.sh Next: 231. P1 instance.sh PROXY_UNIT 86;
bus-service.sh dependency insertion 93. P1 template ExecStart 13;
run.mjs validates config 11-13 and calls startVerifiedProxy 14;
runner.mjs refuses proxy-not-configured 8, verifyInstall 9;
settings.mjs verifyInstall 23-30 refuses proxy-install-invalid for a hash
mismatch. run.mjs catches that and exits 3. P1 item 7 is rows doc 86-90;
row209 entry check 2 remains 32-36.
Add bare --proxy and CASE2_PROXY_TEMPLATE. Render P1's unmodified template
with copy, PATH, node and deleted CONFIG_ENV; add User/Group plus bus's four
hardening lines, no capabilities or EnvironmentFile. check_unit proxy uses
exact node + copy/packages/model-proxy/src/run.mjs, NoNewPrivileges=yes,
and every common refusal. Restrict CLAUDE_ENV expectation and capabilities
to their existing bus/webui kinds. Insert bus Wants/After immediately after
Description exactly as P1 does, only with --proxy. No-proxy output remains
byte identical.
Re-install checks all selected units against render modulo copy id and
compares every installed copy id. Existing proxy without --proxy refuses
before writes, including a dangling symlink. No deletion. Enable all
selected units, then start proxy, bus, Console. Capture/report already-running
proxy with the others and name all three in restart-together guidance.
Change only case2-install.sh's closing Next: to tell root to add --proxy
when the config has a proxy section.
Tests first, unrun until Sage CLEAR: byte-identical non-proxy renders;
proxy content and exact dependencies; each common/proxy check_unit refusal
by message; rendered ExecStart wrong-PIN refusal proxy-install-invalid rc 3;
three-unit re-install and mixed copies including proxy-only mismatch;
installed proxy without flag preserves files; start order and failures;
already-running report; install Next: guidance. Named mutants: remove bus
After=; direct binary ExecStart bypassing run.mjs. Whole-file run list will
be derived from static imports before any run; case1-accept stays off.
P2 is not present on this base (row331 still in progress); inspect landed
P2 refusal code before final handoff. No flag/config false-positive check:
P1 startVerifiedProxy refuses proxy-not-configured, run.mjs exits 3.
## Slot / session
No tests run. CLEAR pending from Sage. Canonical queue start initially
refused: lock held by magica-331-start since 03:57:01.839Z. Same operation
will be retried after a minute; no lock manipulation.
Correction to line references above: row209 entry check 2 is 29-33;
run.mjs config validation 8-10, runner call 11, refusal exit status 18.
case2-unit.sh is byte-identical to brief base 58b2bbb3a (sha256
4ca8769134d83ee391bcc73af5bd042b9e2b161cac22821d29fcca710a9afad4).
P1 paths named above have no diff between 86cb5c221 and 78b69e1e6.
Own-worktree root npm ci --ignore-scripts --no-audit --no-fund: rc 0,
128 packages. No tests invoked.
Sage sage-4346 accepts design, authorizes writing RED tests then code while
holding all runs until CLEAR. Tests were written first, then implementation;
no RED run has occurred. Installed proxy without --proxy explicitly refuses
and preserves bytes; dangling symlink also refuses, never deletes. P2 code
remains pending until row331 lands. Both wrong-flag P1 runner paths covered.
Import-manifest correction: expanded traversal to include multiline import
and export-from edges (the business/library barrel modules). Closure is
53 local modules; only the same two test entrypoints occur. The run list
is unchanged; this is source inspection, no tests executed.
Queue start completed: rockerduck-334-start rev4079, row334
briefed→in-progress. git diff --check passed. Implementation, tests and
import manifest prepared; no tests, commits or pushes performed. Verification
awaits Sage's CLEAR; no live host/instance access occurred.
## RED run authorized by sage-4387, 2026-10-25 04:21Z
Whole files, sequential, with each corresponding script temporarily restored
from 78b69e1e6 and its implementation restored by EXIT trap afterwards:
- case2-unit.test.mjs: rc1, 81 tests, 48 pass, 33 fail, 0 skips;
TMPDIR=/mnt/storage/scratch/tmp/QD3vp5, nice=19, 77995.928687 ms.
All 47 old tests pass; new no-proxy byte equality passes. 32 new failures
encounter baseline unsupported --proxy usage rc4; dangling installed proxy
symlink case gets rc0 rather than refusal because baseline ignores it.
- case2-install.test.mjs: rc1, 28 tests, 27 pass, 1 fail, 0 skips;
TMPDIR=/mnt/storage/scratch/tmp/YAGfKJ, nice=19, 10016.247032 ms.
All old tests pass. New Next: guidance assertion fails because baseline
lacks add --proxy when the config has a proxy section.
Every failure and cause is listed in red-results.txt; full plain-text
outputs in red-unit.log and red-install.log. Absolute node/test paths used.
Implementation restored, git diff --check clean. No old-test red, rerun,
assertion change, GREEN or mutant run. P2 check still pending row331.
Slot returned to Sage; GREEN and mutants await separate CLEAR.
Sage sage-4390 accepts clean RED and explicitly authorizes committing and
pushing this candidate to rockerduck/row334 before GREEN. No force push.
GREEN and mutants remain held for a separate CLEAR after row331 lands;
then rebase onto landed P2 and check its missing-flag refusal code.
## Mos1961 targeted execution — 2026-10-26
Authority: integrate onto refactor09a847ed, preserve old RED, two whole
files serial plus bounded named mutants/restoration. No push, borrowed
posting, live operations or review dispatch. All reviewers Codex; parent
arranges actual new review. Full package/global gate remains Sage's.
Integration: first attempted merge retained both append-only log sides but
merge-continue was refused by the queue guard for incoming queue files.
Diagnosed scripts/git-hooks/pre-commit and packages/queue/README.md's
merge/rebase protocol; aborted merge, rebased normally without disabling
or overriding any hook. Only BUILD-LOG.md and SESSIONS.md conflicted;
retained current refactor log plus all candidate additions. Integrated
source commit c4bba5d2a3936897035c0e8fc8c3e14ca96b73af on exact base
09a847edaa25b609bac7728ff83f224708a29399. Old candidate bd5720662 stays
on origin/rockerduck/row334; no push performed. Original RED files retain
identical bytes (cleanup.txt records their hashes).
Resources: before runs, no other node --test process found, memory
available 29270 MiB, swap free 85176 MiB, memory PSI full avg10 0.28.
Own-worktree npm ci --ignore-scripts --no-audit --no-fund rc0, nice19.
Fresh shell-mktemp short TMPDIR, private HOME/XDG_CONFIG_HOME and explicit
private MOSAIC_CONFIG per run. No real config/keys/units accessed. Absolute
node/test/TAP paths. Existing per-command and per-test deadlines preserved.
Import/export-from graph refreshed in green-imports.txt (56 local modules,
only case2-unit.test.mjs and case2-install.test.mjs as test entrypoints).
Sage-owned case2-precheck.sh and case1-accept.test.mjs unchanged, not run.
Only closing Next: line changed in case2-install.sh.
Measured serial runs (raw TAP beside this file):
- green-unit.tap: rc0, 81/81 pass, zero skips, 78022.489026 ms.
- green-install.tap: rc0, 28/28 pass, zero skips, 8298.345458 ms.
- mutant-after.tap: rc1, selected test 1 fail, zero skips, 259.619391 ms.
Removed only bus After= insertion; expected dependency assertion killed it.
- mutant-exec.tap: rc1, selected test 1 fail, zero skips, 237.081878 ms.
Changed proxy template ExecStart to direct binary; exact ExecStart check
refused, selected content/start-order test killed it. Template restored.
- restored.tap: rc0, 2/2 pass, zero skips, 569.147722 ms, content/start-order
plus rendered ExecStart PIN/no-proxy refusals after both restorations.
Each bounded mutant/restoration command had timeout -k10 60; no timeout hit.
Each TAP header records private paths and measured nice19. No unexpected
red, assertion relaxation, deadline change or blind retry. Mutation diffs
and mutated-source hashes retained, source restored by EXIT traps.
P2 dependency correction: product362893d043ff96fdd35f18e326cce683db031471
landed in refactor; queue331 is tracking-only per Mos1915/#436, not a
product blocker. Landed packages/bus/src/model-keys.mjs:161 and :167 refuse
proxy-down on connection/client failure; :168 refuses non2xx as
proxy-key-refused. P1 runner still refuses proxy-not-configured with exit3
when no proxy section exists. The rendered runner test now passes both
wrong-PIN proxy-install-invalid exit3 and missing-proxy exit3 checks.
This is temp-root test/source evidence; live .53/.54 behavior UNVERIFIED.
Restoration: every green-source.sha256 pin matches after mutants, including
P1 template and PIN. Cleanup: all five logged test HOME/config/TMPDIR roots
removed; no node --test process remains at 22:42:35Z. Test fixture cleanup
completed. Temporary source backup copies were retained outside repository.
Raw evidence hashes in green-evidence.sha256. SLOTBACK: targeted work
complete; source ready for parent-arranged Codex review. Full package/global
landing gate and live install acceptance are not claimed.
@@ -0,0 +1,14 @@
Mos1961 targeted serial-slot cleanup/restoration
green-unit.tap: private root /mnt/storage/scratch/tmp/EHvgUM exists-before=True
removed=True
green-install.tap: private root /mnt/storage/scratch/tmp/iXTGcB exists-before=True
removed=True
mutant-after.tap: private root /mnt/storage/scratch/tmp/3LlK4H exists-before=True
removed=True
mutant-exec.tap: private root /mnt/storage/scratch/tmp/JuznlZ exists-before=True
removed=True
restored.tap: private root /mnt/storage/scratch/tmp/ayiWpX exists-before=True
removed=True
red-unit.log: old RED bytes preserved sha256=8a5c1074b520e3b4ff713e8766cae14b468d0589853499cb4ab0cc8510df3cea
red-install.log: old RED bytes preserved sha256=11138bdf1dcd314e35bf240e0a46c5d9deef204508b5bbec1d298c84a2e05f6f
red-results.txt: old RED bytes preserved sha256=d4e2fcb9c4387c6a328aa8d95576f2ec5a317b1a74210e341026ee89b3c1b76c
@@ -0,0 +1,14 @@
cf0390df3b2800ccfa74ca8e1701de6f23496f12e376f63a3ed415b1e8280ba7 agents/rockerduck/work/queue-334/green-unit.tap
32d48a5bf5a9743e36525cef07c17989d3a4f3304d2c54359d5599ed548dde26 agents/rockerduck/work/queue-334/green-install.tap
4f11595c78fd8bc63ccc6da58fec28231c5fec0a77638cfd1f92a2bcbd5c4ffa agents/rockerduck/work/queue-334/mutant-after.tap
64545ce09254a7fd7674b4b91a19e31d093254917ae4370bb5e6d9372761cdd1 agents/rockerduck/work/queue-334/mutant-exec.tap
b4d9e2f1f42422603c48dbf4fde5a10b789abd4cb46bbd17d759ddae4d26a203 agents/rockerduck/work/queue-334/restored.tap
9d1a7b2c97a75cc22a35e62ab9d617289f62087e02f9dfb6eeb870de1cbd7a1c agents/rockerduck/work/queue-334/green-source.sha256
0081e0326c000c066ac9245f7e31518cbf198d944bf11abc16c8d2379a04b100 agents/rockerduck/work/queue-334/green-imports.txt
06b739ad1947f0059f71af354328fc307da594d262cf4c886a7b73143a872861 agents/rockerduck/work/queue-334/mutant-after.diff
86b9100a1863f6c13e68c6638341659aed917254dee6c61207228fae41138158 agents/rockerduck/work/queue-334/mutant-exec.diff
32b803c436830fc36e4dc5ce48d021d69617b3add0102554558147a20d31401e agents/rockerduck/work/queue-334/mutant-after-source.sha256
eb85013c2b52472d1210a52268ecd5d337ea46026f2cf64cabcc97e168ba9a52 agents/rockerduck/work/queue-334/mutant-exec-source.sha256
acf321d41771070a070d14806c8b84bd6be7c6e30ee95567a22ec2c08918e3bf agents/rockerduck/work/queue-334/restoration.txt
8322de11b7085dc4de6e5be111cb55168598eb2ed631844569c2938c82ac42f7 agents/rockerduck/work/queue-334/cleanup.txt
ecfdf44317fa3ffd544d0f5707970acad2e20cfd1f690a5652a1fdacbf6b83f8 agents/rockerduck/work/queue-334/remaining-test-processes.txt
@@ -0,0 +1,57 @@
Mos1961 static imports/export-from graph; whole files serial, case1-accept excluded.
packages/cli/tests/case2-unit.test.mjs -> packages/business/tests/helpers.mjs, packages/seat/src/sandbox.mjs, packages/cli/tests/helpers.mjs, packages/cli/tests/case-sudo-fixture.mjs
packages/business/tests/helpers.mjs -> (builtins/external only)
packages/seat/src/sandbox.mjs -> packages/business/src/vocabulary.mjs
packages/business/src/vocabulary.mjs -> (builtins/external only)
packages/cli/tests/helpers.mjs -> packages/bus/src/broker.mjs, packages/bus/src/store.mjs, packages/business/tests/helpers.mjs, packages/harness/tests/helpers.mjs, packages/plugins/tests/helpers.mjs, packages/cli/src/transport.mjs
packages/bus/src/broker.mjs -> packages/bus/src/proxy-page-vocabulary.mjs, packages/model-proxy/src/validation.mjs, packages/bus/src/address.mjs, packages/processing-control/src/state.mjs, packages/plugins/src/kinds.mjs, packages/tasks/src/requirements.mjs, packages/bus/src/landing.mjs, packages/business/src/vocabulary.mjs
packages/bus/src/proxy-page-vocabulary.mjs -> (builtins/external only)
packages/model-proxy/src/validation.mjs -> (builtins/external only)
packages/bus/src/address.mjs -> (builtins/external only)
packages/processing-control/src/state.mjs -> (builtins/external only)
packages/plugins/src/kinds.mjs -> (builtins/external only)
packages/tasks/src/requirements.mjs -> (builtins/external only)
packages/bus/src/landing.mjs -> (builtins/external only)
packages/bus/src/store.mjs -> (builtins/external only)
packages/harness/tests/helpers.mjs -> packages/business/src/index.mjs, packages/business/tests/helpers.mjs, packages/plugins/src/loader.mjs
packages/business/src/index.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/role.mjs, packages/business/src/vars.mjs, packages/business/src/credentials.mjs, packages/business/src/business.mjs, packages/business/src/access.mjs, packages/business/src/project.mjs, packages/business/src/resolve.mjs, packages/business/src/library-seats.mjs
packages/business/src/errors.mjs -> (builtins/external only)
packages/business/src/role.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/util.mjs
packages/business/src/util.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs
packages/business/src/vars.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/util.mjs, packages/plugins/src/kinds.mjs
packages/business/src/credentials.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/util.mjs
packages/business/src/business.mjs -> packages/business/src/errors.mjs, packages/business/src/launch.mjs, packages/business/src/vars.mjs, packages/business/src/credentials.mjs, packages/business/src/role.mjs, packages/business/src/vocabulary.mjs, packages/plugins/src/kinds.mjs, packages/library/src/mapping.mjs, packages/business/src/util.mjs
packages/business/src/launch.mjs -> packages/business/src/util.mjs, packages/business/src/errors.mjs
packages/library/src/mapping.mjs -> packages/library/src/problems.mjs
packages/library/src/problems.mjs -> (builtins/external only)
packages/business/src/access.mjs -> packages/business/src/errors.mjs, packages/business/src/business.mjs, packages/business/src/util.mjs, packages/business/src/vocabulary.mjs
packages/business/src/project.mjs -> packages/business/src/errors.mjs, packages/business/src/vars.mjs, packages/business/src/util.mjs
packages/business/src/resolve.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/vars.mjs, packages/business/src/util.mjs, packages/business/src/project.mjs
packages/business/src/library-seats.mjs -> packages/business/src/errors.mjs, packages/business/src/business.mjs, packages/business/src/vocabulary.mjs
packages/plugins/src/loader.mjs -> packages/plugins/src/kinds.mjs
packages/plugins/tests/helpers.mjs -> (builtins/external only)
packages/cli/src/transport.mjs -> packages/cli/src/errors.mjs
packages/cli/src/errors.mjs -> (builtins/external only)
packages/cli/tests/case-sudo-fixture.mjs -> (builtins/external only)
packages/cli/tests/case2-install.test.mjs -> packages/business/tests/helpers.mjs, packages/library/tests/helpers.mjs, packages/library/src/index.mjs, packages/seat/src/checkout.mjs, packages/project/src/git.mjs, packages/cli/tests/helpers.mjs
packages/library/tests/helpers.mjs -> packages/library/src/mapping.mjs
packages/library/src/index.mjs -> packages/library/src/problems.mjs, packages/library/src/mapping.mjs, packages/library/src/scope.mjs, packages/library/src/supporting.mjs, packages/library/src/links.mjs, packages/library/src/reader.mjs, packages/library/src/render.mjs, packages/library/src/survey.mjs, packages/library/src/mirror.mjs, packages/library/src/sync.mjs, packages/library/src/index-build.mjs, packages/library/src/query.mjs, packages/library/src/limits.mjs, packages/library/src/guard.mjs, packages/library/src/write.mjs
packages/library/src/scope.mjs -> (builtins/external only)
packages/library/src/supporting.mjs -> packages/library/src/problems.mjs, packages/library/src/scope.mjs
packages/library/src/links.mjs -> (builtins/external only)
packages/library/src/reader.mjs -> packages/project/src/git.mjs, packages/library/src/scope.mjs, packages/library/src/links.mjs, packages/library/src/problems.mjs
packages/project/src/git.mjs -> packages/seat/src/checkout.mjs, packages/project/src/paths.mjs, packages/processing-control/src/effects.mjs
packages/seat/src/checkout.mjs -> packages/processing-control/src/effects.mjs
packages/processing-control/src/effects.mjs -> packages/processing-control/src/state.mjs, packages/processing-control/src/runtimes.mjs
packages/processing-control/src/runtimes.mjs -> packages/processing-control/src/state.mjs
packages/project/src/paths.mjs -> (builtins/external only)
packages/library/src/render.mjs -> (builtins/external only)
packages/library/src/survey.mjs -> packages/library/src/mapping.mjs, packages/library/src/scope.mjs, packages/library/src/links.mjs, packages/library/src/reader.mjs, packages/library/src/problems.mjs
packages/library/src/mirror.mjs -> packages/project/src/paths.mjs, packages/project/src/git.mjs, packages/library/src/mapping.mjs, packages/library/src/scope.mjs, packages/library/src/problems.mjs, packages/library/src/reader.mjs
packages/library/src/sync.mjs -> packages/seat/src/checkout.mjs, packages/project/src/git.mjs, packages/library/src/mapping.mjs, packages/library/src/problems.mjs, packages/library/src/reader.mjs, packages/library/src/index-build.mjs, packages/library/src/mirror.mjs
packages/library/src/index-build.mjs -> packages/library/src/reader.mjs, packages/library/src/problems.mjs
packages/library/src/query.mjs -> packages/library/src/problems.mjs, packages/library/src/mapping.mjs, packages/library/src/index-build.mjs, packages/library/src/reader.mjs
packages/library/src/limits.mjs -> (builtins/external only)
packages/library/src/guard.mjs -> packages/project/src/git.mjs, packages/library/src/scope.mjs, packages/library/src/reader.mjs, packages/library/src/problems.mjs, packages/library/src/mirror.mjs, packages/library/src/limits.mjs, packages/library/src/reply-protocol.mjs
packages/library/src/reply-protocol.mjs -> (builtins/external only)
packages/library/src/write.mjs -> packages/project/src/git.mjs, packages/seat/src/checkout.mjs, packages/business/src/vocabulary.mjs, packages/library/src/mapping.mjs, packages/library/src/scope.mjs, packages/library/src/supporting.mjs, packages/library/src/problems.mjs, packages/library/src/reader.mjs, packages/library/src/mirror.mjs, packages/library/src/sync.mjs
@@ -0,0 +1,179 @@
# TMPDIR=/mnt/storage/scratch/tmp/iXTGcB HOME=/mnt/storage/scratch/tmp/iXTGcB/home MOSAIC_CONFIG=/mnt/storage/scratch/tmp/iXTGcB/home/.config/mosaic-dev/config.json nice=19
TAP version 13
# Subtest: the library install writes the business file, personas and slot dir, makes the copy and starts nothing
ok 1 - the library install writes the business file, personas and slot dir, makes the copy and starts nothing
---
duration_ms: 247.114691
type: 'test'
...
# Subtest: a library re-run passes and leaves every file as it was
ok 2 - a library re-run passes and leaves every file as it was
---
duration_ms: 409.006062
type: 'test'
...
# Subtest: a re-run refuses business-file-differs and leaves the file unchanged
ok 3 - a re-run refuses business-file-differs and leaves the file unchanged
---
duration_ms: 279.306698
type: 'test'
...
# Subtest: a re-run refuses persona-differs when an installed persona isn't the staged one, and writes nothing
ok 4 - a re-run refuses persona-differs when an installed persona isn't the staged one, and writes nothing
---
duration_ms: 282.805377
type: 'test'
...
# Subtest: the factory install writes no library mapping, persona or slot dir
ok 5 - the factory install writes no library mapping, persona or slot dir
---
duration_ms: 183.788653
type: 'test'
...
# Subtest: the factory install refuses a library project and bus.tester off, before anything is written
ok 6 - the factory install refuses a library project and bus.tester off, before anything is written
---
duration_ms: 75.316181
type: 'test'
...
# Subtest: the library install refuses bus.tester on and a persona var in the input
ok 7 - the library install refuses bus.tester on and a persona var in the input
---
duration_ms: 94.011039
type: 'test'
...
# Subtest: the install refuses root, a business id mismatch, another --home and a config dir with group bits
ok 8 - the install refuses root, a business id mismatch, another --home and a config dir with group bits
---
duration_ms: 68.69039
type: 'test'
...
# Subtest: the library install needs --personas
ok 9 - the library install needs --personas
---
duration_ms: 6.411755
type: 'test'
...
# Subtest: a validate refusal removes the business file this run wrote
ok 10 - a validate refusal removes the business file this run wrote
---
duration_ms: 254.969107
type: 'test'
...
# Subtest: N4: a validate refusal on a re-run leaves the matching business file in place, byte for byte
ok 11 - N4: a validate refusal on a re-run leaves the matching business file in place, byte for byte
---
duration_ms: 424.725266
type: 'test'
...
# Subtest: a clean library host passes the precheck with no mirror yet
ok 12 - a clean library host passes the precheck with no mirror yet
---
duration_ms: 435.686925
type: 'test'
...
# Subtest: the library precheck refuses a persona file at 0644
ok 13 - the library precheck refuses a persona file at 0644
---
duration_ms: 417.655889
type: 'test'
...
# Subtest: the library precheck refuses a persona whose digest isn't persona.sha256
ok 14 - the library precheck refuses a persona whose digest isn't persona.sha256
---
duration_ms: 411.08414
type: 'test'
...
# Subtest: the library precheck refuses a persona dir with group bits
ok 15 - the library precheck refuses a persona dir with group bits
---
duration_ms: 451.558674
type: 'test'
...
# Subtest: the library precheck refuses a business file at 0644
ok 16 - the library precheck refuses a business file at 0644
---
duration_ms: 435.345965
type: 'test'
...
# Subtest: the library precheck refuses a changes.jsonl at 0644
ok 17 - the library precheck refuses a changes.jsonl at 0644
---
duration_ms: 584.394398
type: 'test'
...
# Subtest: the library precheck refuses a Discord binding
ok 18 - the library precheck refuses a Discord binding
---
duration_ms: 441.83167
type: 'test'
...
# Subtest: --library-status refuses library-mirror-missing before the bus has made the mirror
ok 19 - --library-status refuses library-mirror-missing before the bus has made the mirror
---
duration_ms: 337.042932
type: 'test'
...
# Subtest: --library-status refuses library-never-synced on a fresh mirror
ok 20 - --library-status refuses library-never-synced on a fresh mirror
---
duration_ms: 350.216282
type: 'test'
...
# Subtest: --library-status names a recorded lastError before the null lastFetch it leaves (N3)
ok 21 - --library-status names a recorded lastError before the null lastFetch it leaves (N3)
---
duration_ms: 369.786709
type: 'test'
...
# Subtest: --library-status prints each R-problem's path as an escaped JSON string, then the count, and passes
ok 22 - --library-status prints each R-problem's path as an escaped JSON string, then the count, and passes
---
duration_ms: 410.961598
type: 'test'
...
# Subtest: --library-status takes no --profile
ok 23 - --library-status takes no --profile
---
duration_ms: 5.4699
type: 'test'
...
# Subtest: Vikunja 614: install preserves the explicit notifier decision bytes and mode and creates none when absent
ok 24 - Vikunja 614: install preserves the explicit notifier decision bytes and mode and creates none when absent
---
duration_ms: 479.343091
type: 'test'
...
# Subtest: row 322 F8: library personas install from the copy with no clone node_modules
ok 25 - row 322 F8: library personas install from the copy with no clone node_modules
---
duration_ms: 146.617921
type: 'test'
...
# Subtest: row 322 F9: importer refusal keeps the error line rather than the Node banner
ok 26 - row 322 F9: importer refusal keeps the error line rather than the Node banner
---
duration_ms: 108.029088
type: 'test'
...
# Subtest: C2-21: library install supplies chief identity and books publish for its real read grant
ok 27 - C2-21: library install supplies chief identity and books publish for its real read grant
---
duration_ms: 236.226186
type: 'test'
...
# Subtest: row334 install Next tells root to select proxy only when configured
ok 28 - row334 install Next tells root to select proxy only when configured
---
duration_ms: 242.832964
type: 'test'
...
1..28
# tests 28
# suites 0
# pass 28
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 8298.345458
@@ -0,0 +1,6 @@
30d9674f6b5b704b82bf7dfcf054b425d6b9480144ab52bb00f668af514c17bd scripts/case2-unit.sh
9e52d30113ae1cfa82f947674a4132dd689fc43ac1a1e8cdc4bce0c71e42fde1 scripts/case2-install.sh
373f64bf56f031a1d76873f1444b35f2de2a8e26a84dca9107f1993dbd9669bb packages/cli/tests/case2-unit.test.mjs
716f7728f018b16f917e60547459f6791dab5d3239dd4dcda5a7365f54850ade packages/cli/tests/case2-install.test.mjs
16c31f119d064a836794d167d57a6a1b765303005d93d1feba3e07e4b9288bd2 packages/cli/systemd/mosaic-proxy.service.in
889ac9589fadf7654d4dbb914f968cbafe4255ed215bd6a9ff3f331757c9017b packages/model-proxy/PIN
@@ -0,0 +1,497 @@
# TMPDIR=/mnt/storage/scratch/tmp/EHvgUM HOME=/mnt/storage/scratch/tmp/EHvgUM/home MOSAIC_CONFIG=/mnt/storage/scratch/tmp/EHvgUM/home/.config/mosaic-dev/config.json nice=19
TAP version 13
# Subtest: row253 factory socket uses actual config and target-uid query without widening hardening
ok 1 - row253 factory socket uses actual config and target-uid query without widening hardening
---
duration_ms: 320.786952
type: 'test'
...
# Subtest: row253 installer refuses unsafe location and query facts before unit writes
ok 2 - row253 installer refuses unsafe location and query facts before unit writes
---
duration_ms: 3259.255387
type: 'test'
...
# Subtest: row253 query timeout kills its process group and leaves no running child
ok 3 - row253 query timeout kills its process group and leaves no running child
---
duration_ms: 7050.967328
type: 'test'
...
# Subtest: row253 protected homes refuse before any filesystem access
ok 4 - row253 protected homes refuse before any filesystem access
---
duration_ms: 9.235035
type: 'test'
...
# Subtest: row253 C4 sleep query has exact inner reason and cleans all members
ok 5 - row253 C4 sleep query has exact inner reason and cleans all members
---
duration_ms: 10728.278487
type: 'test'
...
# Subtest: row253 C4 signals query has exact inner reason and cleans all members
ok 6 - row253 C4 signals query has exact inner reason and cleans all members
---
duration_ms: 10727.497398
type: 'test'
...
# Subtest: row253 C4 member query has exact inner reason and cleans all members
ok 7 - row253 C4 member query has exact inner reason and cleans all members
---
duration_ms: 10864.916145
type: 'test'
...
# Subtest: row253 C4 overflow is output-limit in both sudo modes
ok 8 - row253 C4 overflow is output-limit in both sudo modes
---
duration_ms: 471.505102
type: 'test'
...
# Subtest: row253 C4 stalled supervisor uses the outer reason and explicit residual cleanup
ok 9 - row253 C4 stalled supervisor uses the outer reason and explicit residual cleanup
---
duration_ms: 14141.133414
type: 'test'
...
# Subtest: row253 C4 inline supervisor is byte-identical in both host scripts
ok 10 - row253 C4 inline supervisor is byte-identical in both host scripts
---
duration_ms: 1.040122
type: 'test'
...
# Subtest: row253 C4 cleanup budget starts after inventory pass one
ok 11 - row253 C4 cleanup budget starts after inventory pass one
---
duration_ms: 36.998899
type: 'test'
...
# Subtest: row253 query reaps its group even when a child closes stdout before the query returns
ok 12 - row253 query reaps its group even when a child closes stdout before the query returns
---
duration_ms: 128.332368
type: 'test'
...
# Subtest: row253 config parser is strict
ok 13 - row253 config parser is strict
---
duration_ms: 8.326775
type: 'test'
...
# Subtest: a clean library render writes both units 0644 with the fixed lines, and enables both then starts the bus then the Console
ok 14 - a clean library render writes both units 0644 with the fixed lines, and enables both then starts the bus then the Console
---
duration_ms: 95.375021
type: 'test'
...
# Subtest: PATH starts with the --node directory, for a node outside the default list and for /usr/bin/node
ok 15 - PATH starts with the --node directory, for a node outside the default list and for /usr/bin/node
---
duration_ms: 107.764189
type: 'test'
...
# Subtest: a failed bus start refuses with the Console not started
ok 16 - a failed bus start refuses with the Console not started
---
duration_ms: 95.26919
type: 'test'
...
# Subtest: the script never links a unit file
ok 17 - the script never links a unit file
---
duration_ms: 0.161935
type: 'test'
...
# Subtest: factory renders --pm once and one MOSAIC_CLAUDE_BIN line, and without them both tokens render empty
ok 18 - factory renders --pm once and one MOSAIC_CLAUDE_BIN line, and without them both tokens render empty
---
duration_ms: 601.778739
type: 'test'
...
# Subtest: a re-install over running units says they are still on the old copy and must restart together, never that it started them
ok 19 - a re-install over running units says they are still on the old copy and must restart together, never that it started them
---
duration_ms: 414.344645
type: 'test'
...
# Subtest: a re-install to another copy re-renders both units, and an edited installed unit refuses
ok 20 - a re-install to another copy re-renders both units, and an edited installed unit refuses
---
duration_ms: 994.134837
type: 'test'
...
# Subtest: a template with a namespace, user or system call restriction refuses
ok 21 - a template with a namespace, user or system call restriction refuses
---
duration_ms: 263.798116
type: 'test'
...
# Subtest: the bus template's EnvironmentFile line must be there and point at the session env, and the Console template must have none
ok 22 - the bus template's EnvironmentFile line must be there and point at the session env, and the Console template must have none
---
duration_ms: 181.439557
type: 'test'
...
# Subtest: a template holding an unknown token, PermissionsStartOnly or a privileged Exec prefix refuses
ok 23 - a template holding an unknown token, PermissionsStartOnly or a privileged Exec prefix refuses
---
duration_ms: 138.913066
type: 'test'
...
# Subtest: a symlink at either unit path refuses
ok 24 - a symlink at either unit path refuses
---
duration_ms: 167.097448
type: 'test'
...
# Subtest: a --node that is relative, not root-owned or under <home> refuses
ok 25 - a --node that is relative, not root-owned or under <home> refuses
---
duration_ms: 18.382732
type: 'test'
...
# Subtest: a re-install with a different --node or different serve flags refuses
ok 26 - a re-install with a different --node or different serve flags refuses
---
duration_ms: 476.601285
type: 'test'
...
# Subtest: an installed pair whose units name different copies refuses
ok 27 - an installed pair whose units name different copies refuses
---
duration_ms: 244.045399
type: 'test'
...
# Subtest: a copy id that is short, uppercase, holds / or .., or names no directory refuses
ok 28 - a copy id that is short, uppercase, holds / or .., or names no directory refuses
---
duration_ms: 28.577084
type: 'test'
...
# Subtest: library refuses a privileged-floor miss, a bad proof pair and every flag outside its set
ok 29 - library refuses a privileged-floor miss, a bad proof pair and every flag outside its set
---
duration_ms: 270.700937
type: 'test'
...
# Subtest: library refuses a proof file not owned by <user>'s uid
ok 30 - library refuses a proof file not owned by <user>'s uid
---
duration_ms: 15.926859
type: 'test'
...
# Subtest: factory refuses duplicate socket, a missing --port or --business, and each proxy flag
ok 31 - factory refuses duplicate socket, a missing --port or --business, and each proxy flag
---
duration_ms: 178.797946
type: 'test'
...
# Subtest: row253 factory refuses retired TCP and a value after the socket marker
ok 32 - row253 factory refuses retired TCP and a value after the socket marker
---
duration_ms: 26.729147
type: 'test'
...
# Subtest: factory refuses a --claude that is relative, not root-owned or under <home>, and a re-install with another --pm or --claude
ok 33 - factory refuses a --claude that is relative, not root-owned or under <home>, and a re-install with another --pm or --claude
---
duration_ms: 1287.523935
type: 'test'
...
# Subtest: the precheck passes on a good host and prints free memory, without the key
ok 34 - the precheck passes on a good host and prints free memory, without the key
---
duration_ms: 165.265761
type: 'test'
...
# Subtest: the precheck refuses each failed host check
ok 35 - the precheck refuses each failed host check
---
duration_ms: 1355.479314
type: 'test'
...
# Subtest: the precheck refuses a user missing from passwd, a business file that does not load, and a symlinked run root
ok 36 - the precheck refuses a user missing from passwd, a business file that does not load, and a symlinked run root
---
duration_ms: 749.037298
type: 'test'
...
# Subtest: a bwrap that fails refuses and prints the sysctl and where bwrap is
ok 37 - a bwrap that fails refuses and prints the sysctl and where bwrap is
---
duration_ms: 158.018943
type: 'test'
...
# Subtest: an unprivileged port floor at or below the Console's port refuses, and so does a missing Console unit
ok 38 - an unprivileged port floor at or below the Console's port refuses, and so does a missing Console unit
---
duration_ms: 775.768583
type: 'test'
...
# Subtest: bus.tester: library refuses it on, factory needs it on, and factory refuses a library business
ok 39 - bus.tester: library refuses it on, factory needs it on, and factory refuses a library business
---
duration_ms: 591.243454
type: 'test'
...
# Subtest: the claude check runs only for a claude-code instance, and refuses a claude that appends or answers wrong
ok 40 - the claude check runs only for a claude-code instance, and refuses a claude that appends or answers wrong
---
duration_ms: 1192.916891
type: 'test'
...
# Subtest: the claude check runs the bus unit's MOSAIC_CLAUDE_BIN over claude on PATH
ok 41 - the claude check runs the bus unit's MOSAIC_CLAUDE_BIN over claude on PATH
---
duration_ms: 380.298256
type: 'test'
...
# Subtest: the session env file refuses when missing, open to others, a symlink, empty, or emptied by a later line
ok 42 - the session env file refuses when missing, open to others, a symlink, empty, or emptied by a later line
---
duration_ms: 944.757771
type: 'test'
...
# Subtest: inside a session's sandbox a bind below 1024 fails with EACCES, and a bind on a free high port succeeds
ok 43 - inside a session's sandbox a bind below 1024 fails with EACCES, and a bind on a free high port succeeds
---
duration_ms: 94.761095
type: 'test'
...
# Subtest: Vikunja 614 factory: scoped @anthropic-ai Claude path renders and passes installed read-back
ok 44 - Vikunja 614 factory: scoped @anthropic-ai Claude path renders and passes installed read-back
---
duration_ms: 804.156726
type: 'test'
...
# Subtest: Vikunja 614 factory: plain paths still refuse every named unsafe character
ok 45 - Vikunja 614 factory: plain paths still refuse every named unsafe character
---
duration_ms: 87.565396
type: 'test'
...
# Subtest: Vikunja 614 precheck: explicit null and named notifier decisions pass
ok 46 - Vikunja 614 precheck: explicit null and named notifier decisions pass
---
duration_ms: 305.744175
type: 'test'
...
# Subtest: Vikunja 614 precheck: notifier config refuses missing, public, malformed and extra-key decisions
ok 47 - Vikunja 614 precheck: notifier config refuses missing, public, malformed and extra-key decisions
---
duration_ms: 1300.724491
type: 'test'
...
# Subtest: row334 no-proxy renders stay byte-identical to the pre-row renderer
ok 48 - row334 no-proxy renders stay byte-identical to the pre-row renderer
---
duration_ms: 257.734638
type: 'test'
...
# Subtest: row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order
ok 49 - row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order
---
duration_ms: 137.176987
type: 'test'
...
# Subtest: row334 proxy refuses continuation by message before writing
ok 50 - row334 proxy refuses continuation by message before writing
---
duration_ms: 90.060095
type: 'test'
...
# Subtest: row334 proxy refuses not-setting by message before writing
ok 51 - row334 proxy refuses not-setting by message before writing
---
duration_ms: 93.993348
type: 'test'
...
# Subtest: row334 proxy refuses RestrictNamespaces by message before writing
ok 52 - row334 proxy refuses RestrictNamespaces by message before writing
---
duration_ms: 93.341838
type: 'test'
...
# Subtest: row334 proxy refuses PrivateUsers by message before writing
ok 53 - row334 proxy refuses PrivateUsers by message before writing
---
duration_ms: 94.764023
type: 'test'
...
# Subtest: row334 proxy refuses SystemCallFilter by message before writing
ok 54 - row334 proxy refuses SystemCallFilter by message before writing
---
duration_ms: 94.826317
type: 'test'
...
# Subtest: row334 proxy refuses PermissionsStartOnly by message before writing
ok 55 - row334 proxy refuses PermissionsStartOnly by message before writing
---
duration_ms: 96.116509
type: 'test'
...
# Subtest: row334 proxy refuses EnvironmentFile by message before writing
ok 56 - row334 proxy refuses EnvironmentFile by message before writing
---
duration_ms: 119.193391
type: 'test'
...
# Subtest: row334 proxy refuses AmbientCapabilities by message before writing
ok 57 - row334 proxy refuses AmbientCapabilities by message before writing
---
duration_ms: 116.325629
type: 'test'
...
# Subtest: row334 proxy refuses CapabilityBoundingSet by message before writing
ok 58 - row334 proxy refuses CapabilityBoundingSet by message before writing
---
duration_ms: 117.596342
type: 'test'
...
# Subtest: row334 proxy refuses privileged by message before writing
ok 59 - row334 proxy refuses privileged by message before writing
---
duration_ms: 98.105075
type: 'test'
...
# Subtest: row334 proxy refuses direct-binary by message before writing
ok 60 - row334 proxy refuses direct-binary by message before writing
---
duration_ms: 114.60378
type: 'test'
...
# Subtest: row334 proxy refuses nnp by message before writing
ok 61 - row334 proxy refuses nnp by message before writing
---
duration_ms: 103.113825
type: 'test'
...
# Subtest: row334 proxy refuses cwd by message before writing
ok 62 - row334 proxy refuses cwd by message before writing
---
duration_ms: 102.872946
type: 'test'
...
# Subtest: row334 proxy refuses environment by message before writing
ok 63 - row334 proxy refuses environment by message before writing
---
duration_ms: 104.833099
type: 'test'
...
# Subtest: row334 proxy refuses install by message before writing
ok 64 - row334 proxy refuses install by message before writing
---
duration_ms: 113.150594
type: 'test'
...
# Subtest: row334 proxy refuses user by message before writing
ok 65 - row334 proxy refuses user by message before writing
---
duration_ms: 99.6967
type: 'test'
...
# Subtest: row334 proxy refuses group by message before writing
ok 66 - row334 proxy refuses group by message before writing
---
duration_ms: 100.919966
type: 'test'
...
# Subtest: row334 proxy refuses ProtectSystem by message before writing
ok 67 - row334 proxy refuses ProtectSystem by message before writing
---
duration_ms: 105.543055
type: 'test'
...
# Subtest: row334 proxy refuses ReadWritePaths by message before writing
ok 68 - row334 proxy refuses ReadWritePaths by message before writing
---
duration_ms: 107.705639
type: 'test'
...
# Subtest: row334 proxy refuses ProtectHome by message before writing
ok 69 - row334 proxy refuses ProtectHome by message before writing
---
duration_ms: 107.590511
type: 'test'
...
# Subtest: row334 proxy refuses PrivateTmp by message before writing
ok 70 - row334 proxy refuses PrivateTmp by message before writing
---
duration_ms: 109.894567
type: 'test'
...
# Subtest: row334 proxy refuses token by message before writing
ok 71 - row334 proxy refuses token by message before writing
---
duration_ms: 85.918305
type: 'test'
...
# Subtest: row334 proxy template missing or symlink refuses
ok 72 - row334 proxy template missing or symlink refuses
---
duration_ms: 167.940142
type: 'test'
...
# Subtest: row334 rendered ExecStart refuses wrong PIN and missing proxy with P1 status
ok 73 - row334 rendered ExecStart refuses wrong PIN and missing proxy with P1 status
---
duration_ms: 306.45812
type: 'test'
...
# Subtest: row334 reinstall repins all three and refuses a proxy copy mismatch
ok 74 - row334 reinstall repins all three and refuses a proxy copy mismatch
---
duration_ms: 580.756121
type: 'test'
...
# Subtest: row334 installed proxy without --proxy refuses and preserves all bytes
ok 75 - row334 installed proxy without --proxy refuses and preserves all bytes
---
duration_ms: 217.139684
type: 'test'
...
# Subtest: row334 dangling proxy symlink without flag refuses without deleting
ok 76 - row334 dangling proxy symlink without flag refuses without deleting
---
duration_ms: 115.14093
type: 'test'
...
# Subtest: row334 active proxy reports old copy and all three must restart together
ok 77 - row334 active proxy reports old copy and all three must restart together
---
duration_ms: 367.168744
type: 'test'
...
# Subtest: row334 failed mosaic-proxy.service start leaves later units unstarted
ok 78 - row334 failed mosaic-proxy.service start leaves later units unstarted
---
duration_ms: 138.123277
type: 'test'
...
# Subtest: row334 failed [email protected] start leaves later units unstarted
ok 79 - row334 failed [email protected] start leaves later units unstarted
---
duration_ms: 132.720562
type: 'test'
...
# Subtest: row334 installed proxy enforces hardening order and remaining common refusals
ok 80 - row334 installed proxy enforces hardening order and remaining common refusals
---
duration_ms: 667.650113
type: 'test'
...
# Subtest: row334 proxy installed path rejects symlink and non-regular file
ok 81 - row334 proxy installed path rejects symlink and non-regular file
---
duration_ms: 239.721676
type: 'test'
...
1..81
# tests 81
# suites 0
# pass 81
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 78022.489026
@@ -0,0 +1,69 @@
# Row334 whole-file run list (UNRUN; awaiting CLEAR)
Entrypoints are the brief-owned tests, which spawn the changed shell scripts.
Their import graph is recorded below; no imported module is itself a test entrypoint.
case1-accept.test.mjs is excluded (mos-1612).
packages/cli/tests/case2-unit.test.mjs -> packages/business/tests/helpers.mjs, packages/seat/src/sandbox.mjs, packages/cli/tests/helpers.mjs, packages/cli/tests/case-sudo-fixture.mjs
packages/business/tests/helpers.mjs -> (builtins/external only)
packages/seat/src/sandbox.mjs -> packages/business/src/vocabulary.mjs
packages/business/src/vocabulary.mjs -> (builtins/external only)
packages/cli/tests/helpers.mjs -> packages/bus/src/broker.mjs, packages/bus/src/store.mjs, packages/business/tests/helpers.mjs, packages/harness/tests/helpers.mjs, packages/plugins/tests/helpers.mjs, packages/cli/src/transport.mjs
packages/bus/src/broker.mjs -> packages/bus/src/address.mjs, packages/processing-control/src/state.mjs, packages/plugins/src/kinds.mjs, packages/tasks/src/requirements.mjs, packages/bus/src/landing.mjs, packages/business/src/vocabulary.mjs
packages/bus/src/address.mjs -> (builtins/external only)
packages/processing-control/src/state.mjs -> (builtins/external only)
packages/plugins/src/kinds.mjs -> (builtins/external only)
packages/tasks/src/requirements.mjs -> (builtins/external only)
packages/bus/src/landing.mjs -> (builtins/external only)
packages/bus/src/store.mjs -> (builtins/external only)
packages/harness/tests/helpers.mjs -> packages/business/src/index.mjs, packages/business/tests/helpers.mjs, packages/plugins/src/loader.mjs
packages/business/src/index.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/role.mjs, packages/business/src/vars.mjs, packages/business/src/credentials.mjs, packages/business/src/business.mjs, packages/business/src/access.mjs, packages/business/src/project.mjs, packages/business/src/resolve.mjs, packages/business/src/library-seats.mjs
packages/business/src/errors.mjs -> (builtins/external only)
packages/business/src/role.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/util.mjs
packages/business/src/util.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs
packages/business/src/vars.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/util.mjs, packages/plugins/src/kinds.mjs
packages/business/src/credentials.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/util.mjs
packages/business/src/business.mjs -> packages/business/src/errors.mjs, packages/business/src/launch.mjs, packages/business/src/vars.mjs, packages/business/src/credentials.mjs, packages/business/src/role.mjs, packages/business/src/vocabulary.mjs, packages/plugins/src/kinds.mjs, packages/library/src/mapping.mjs, packages/business/src/util.mjs
packages/business/src/launch.mjs -> packages/business/src/util.mjs, packages/business/src/errors.mjs
packages/library/src/mapping.mjs -> packages/library/src/problems.mjs
packages/library/src/problems.mjs -> (builtins/external only)
packages/business/src/access.mjs -> packages/business/src/errors.mjs, packages/business/src/business.mjs, packages/business/src/util.mjs, packages/business/src/vocabulary.mjs
packages/business/src/project.mjs -> packages/business/src/errors.mjs, packages/business/src/vars.mjs, packages/business/src/util.mjs
packages/business/src/resolve.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/vars.mjs, packages/business/src/util.mjs, packages/business/src/project.mjs
packages/business/src/library-seats.mjs -> packages/business/src/errors.mjs, packages/business/src/business.mjs, packages/business/src/vocabulary.mjs
packages/plugins/src/loader.mjs -> packages/plugins/src/kinds.mjs
packages/plugins/tests/helpers.mjs -> (builtins/external only)
packages/cli/src/transport.mjs -> packages/cli/src/errors.mjs
packages/cli/src/errors.mjs -> (builtins/external only)
packages/cli/tests/case-sudo-fixture.mjs -> (builtins/external only)
packages/cli/tests/case2-install.test.mjs -> packages/business/tests/helpers.mjs, packages/library/tests/helpers.mjs, packages/library/src/index.mjs, packages/seat/src/checkout.mjs, packages/project/src/git.mjs, packages/cli/tests/helpers.mjs
packages/library/tests/helpers.mjs -> packages/library/src/mapping.mjs
packages/library/src/index.mjs -> packages/library/src/problems.mjs, packages/library/src/mapping.mjs, packages/library/src/scope.mjs, packages/library/src/supporting.mjs, packages/library/src/links.mjs, packages/library/src/reader.mjs, packages/library/src/render.mjs, packages/library/src/survey.mjs, packages/library/src/mirror.mjs, packages/library/src/sync.mjs, packages/library/src/index-build.mjs, packages/library/src/query.mjs, packages/library/src/limits.mjs, packages/library/src/guard.mjs
packages/library/src/scope.mjs -> (builtins/external only)
packages/library/src/supporting.mjs -> packages/library/src/problems.mjs, packages/library/src/scope.mjs
packages/library/src/links.mjs -> (builtins/external only)
packages/library/src/reader.mjs -> packages/project/src/git.mjs, packages/library/src/scope.mjs, packages/library/src/links.mjs, packages/library/src/problems.mjs
packages/project/src/git.mjs -> packages/seat/src/checkout.mjs, packages/project/src/paths.mjs, packages/processing-control/src/effects.mjs
packages/seat/src/checkout.mjs -> packages/processing-control/src/effects.mjs
packages/processing-control/src/effects.mjs -> packages/processing-control/src/state.mjs, packages/processing-control/src/runtimes.mjs
packages/processing-control/src/runtimes.mjs -> packages/processing-control/src/state.mjs
packages/project/src/paths.mjs -> (builtins/external only)
packages/library/src/render.mjs -> (builtins/external only)
packages/library/src/survey.mjs -> packages/library/src/mapping.mjs, packages/library/src/scope.mjs, packages/library/src/links.mjs, packages/library/src/reader.mjs, packages/library/src/problems.mjs
packages/library/src/mirror.mjs -> packages/project/src/paths.mjs, packages/project/src/git.mjs, packages/library/src/mapping.mjs, packages/library/src/scope.mjs, packages/library/src/problems.mjs, packages/library/src/reader.mjs
packages/library/src/sync.mjs -> packages/seat/src/checkout.mjs, packages/project/src/git.mjs, packages/library/src/mapping.mjs, packages/library/src/problems.mjs, packages/library/src/reader.mjs, packages/library/src/mirror.mjs
packages/library/src/index-build.mjs -> packages/library/src/reader.mjs, packages/library/src/problems.mjs
packages/library/src/query.mjs -> packages/library/src/problems.mjs, packages/library/src/mapping.mjs, packages/library/src/index-build.mjs, packages/library/src/reader.mjs
packages/library/src/limits.mjs -> (builtins/external only)
packages/library/src/guard.mjs -> packages/project/src/git.mjs, packages/library/src/scope.mjs, packages/library/src/reader.mjs, packages/library/src/problems.mjs, packages/library/src/mirror.mjs, packages/library/src/limits.mjs, packages/library/src/reply-protocol.mjs
packages/library/src/reply-protocol.mjs -> (builtins/external only)
Whole-file commands after CLEAR (one fresh shell-mktemp TMPDIR each; nice=19):
node --test packages/cli/tests/case2-unit.test.mjs
node --test packages/cli/tests/case2-install.test.mjs
Before these: new row334 tests only on saved 78b69e1e6 scripts for RED,
then restored implementation. After GREEN: named mutants individually,
each with its directly relevant row334 test. No scripts/test-*.sh builder run planned.
Includes multiline imports and export-from module edges: 53 local modules.
@@ -0,0 +1 @@
3260d5f6498aaaf810412356c0fd2e1ff10e91f481c8b365ca32e6e4165166d9 scripts/case2-unit.sh
@@ -0,0 +1,13 @@
diff --git a/scripts/case2-unit.sh b/scripts/case2-unit.sh
index bf5097abb..d8a1aae18 100755
--- a/scripts/case2-unit.sh
+++ b/scripts/case2-unit.sh
@@ -456,7 +456,7 @@ render() {
local add="User=$USER_NAME\nGroup=$GROUP\n${HARDENING//$'\n'/\\n}\n"
[ "$kind" != webui ] || add="$add${CAPS//$'\n'/\\n}\n"
if [ "$kind" = bus ] && [ "$PROXY" = 1 ]; then
- text="$(printf '%s\n' "$text" | sed "/^Description=/a Wants=$PROXY_UNIT\\nAfter=$PROXY_UNIT")"
+ text="$(printf '%s\n' "$text" | sed "/^Description=/a Wants=$PROXY_UNIT")"
fi
# The added lines go at the end of [Service], before its trailing blank
# lines. In [Install], WantedBy=multi-user.target replaces
@@ -0,0 +1,133 @@
# TMPDIR=/mnt/storage/scratch/tmp/3LlK4H HOME=/mnt/storage/scratch/tmp/3LlK4H/home MOSAIC_CONFIG=/mnt/storage/scratch/tmp/3LlK4H/home/.config/mosaic-dev/config.json nice=19 budget=60s
TAP version 13
# Subtest: row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order
not ok 1 - row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order
---
duration_ms: 139.480123
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:966:1'
failureType: 'testCodeFailure'
error: |-
The input did not match the regular expression /Description=[^\n]+\nWants=mosaic-proxy.service\nAfter=mosaic-proxy.service\n/. Input:
'# Mosaic bus host, one instance per business: mosaic-bus@<business>.\n' +
'# Rendered by scripts/bus-service.sh from packages/cli/systemd/; /mnt/storage/scratch/tmp/3LlK4H/case2-unit-jxPM4A/home/mosaic/.local/share/mosaic-stack/copies/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,\n' +
'# /usr/bin:/usr/local/bin:/bin and the claude line below are filled in at install time. Edit the template and reinstall; do\n' +
'# not edit the installed copy. The bus store allows one broker per data\n' +
'# root, so run one instance per data root (packages/cli/README.md).\n' +
'[Unit]\n' +
'Description=Mosaic bus host (%i)\n' +
'Wants=mosaic-proxy.service\n' +
'Documentation=file:///mnt/storage/scratch/tmp/3LlK4H/case2-unit-jxPM4A/home/mosaic/.local/share/mosaic-stack/copies/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/packages/cli/README.md\n' +
'StartLimitIntervalSec=600\n' +
'StartLimitBurst=5\n' +
'\n' +
'[Service]\n' +
'Type=simple\n' +
'WorkingDirectory=/mnt/storage/scratch/tmp/3LlK4H/case2-unit-jxPM4A/home/mosaic/.local/share/mosaic-stack/copies/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n' +
'Environment=PATH=/usr/bin:/usr/local/bin:/bin\n' +
"# The Claude Code binary that managed sessions and the manifest's\n" +
'# claudeVersion run, pinned at install (`--claude PATH`, default\n' +
'# `command -v claude`). Without it they run `claude` from PATH above.\n' +
'Environment=MOSAIC_CLAUDE_BIN=/usr/bin/true\n' +
'# Claude Code sessions log in with what this 0600 file sets, which the\n' +
"# operator writes: CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY, or a proxy's\n" +
'# ANTHROPIC_AUTH_TOKEN with ANTHROPIC_BASE_URL. The `-` lets the unit start\n' +
'# without it, and a Claude Code launch then refuses.\n' +
'EnvironmentFile=-/mnt/storage/scratch/tmp/3LlK4H/case2-unit-jxPM4A/home/mosaic/.config/mosaic-dev/secrets/claude-session.env\n' +
'# The host reads the system config and the business file, boots the broker\n' +
'# (packages/bus/src/process.mjs) and starts the notifier. Config problems and\n' +
'# refusals exit 3, usage 4, invalid input 2: none of those is retried. A\n' +
'# child that dies takes the host down with exit 1, and the unit restarts\n' +
'# the pair.\n' +
'ExecStart=/mnt/storage/scratch/tmp/3LlK4H/case2-unit-jxPM4A/home/mosaic/.local/share/mosaic-stack/copies/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/scripts/mosaic bus start %i\n' +
'Restart=on-failure\n' +
'RestartSec=15\n' +
'RestartPreventExitStatus=2 3 4\n' +
'# `systemctl --user stop` sends SIGTERM to the host, which stops the\n' +
'# notifier after its poll in flight, then closes the broker.\n' +
'KillSignal=SIGTERM\n' +
'KillMode=mixed\n' +
'TimeoutStopSec=60\n' +
'NoNewPrivileges=yes\n' +
'User=mosaic\n' +
'Group=mosaic\n' +
'ProtectSystem=strict\n' +
'ReadWritePaths=/mnt/storage/scratch/tmp/3LlK4H/case2-unit-jxPM4A/home/mosaic\n' +
'ProtectHome=yes\n' +
'PrivateTmp=yes\n' +
'\n' +
'[Install]\n' +
'WantedBy=multi-user.target\n'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
actual: |-
# Mosaic bus host, one instance per business: mosaic-bus@<business>.
# Rendered by scripts/bus-service.sh from packages/cli/systemd/; /mnt/storage/scratch/tmp/3LlK4H/case2-unit-jxPM4A/home/mosaic/.local/share/mosaic-stack/copies/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
# /usr/bin:/usr/local/bin:/bin and the claude line below are filled in at install time. Edit the template and reinstall; do
# not edit the installed copy. The bus store allows one broker per data
# root, so run one instance per data root (packages/cli/README.md).
[Unit]
Description=Mosaic bus host (%i)
Wants=mosaic-proxy.service
Documentation=file:///mnt/storage/scratch/tmp/3LlK4H/case2-unit-jxPM4A/home/mosaic/.local/share/mosaic-stack/copies/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/packages/cli/README.md
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=/mnt/storage/scratch/tmp/3LlK4H/case2-unit-jxPM4A/home/mosaic/.local/share/mosaic-stack/copies/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Environment=PATH=/usr/bin:/usr/local/bin:/bin
# The Claude Code binary that managed sessions and the manifest's
# claudeVersion run, pinned at install (`--claude PATH`, default
# `command -v claude`). Without it they run `claude` from PATH above.
Environment=MOSAIC_CLAUDE_BIN=/usr/bin/true
# Claude Code sessions log in with what this 0600 file sets, which the
# operator writes: CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY, or a proxy's
# ANTHROPIC_AUTH_TOKEN with ANTHROPIC_BASE_URL. The `-` lets the unit start
# without it, and a Claude Code launch then refuses.
EnvironmentFile=-/mnt/storage/scratch/tmp/3LlK4H/case2-unit-jxPM4A/home/mosaic/.config/mosaic-dev/secrets/claude-session.env
# The host reads the system config and the business file, boots the broker
# (packages/bus/src/process.mjs) and starts the notifier. Config problems and
# refusals exit 3, usage 4, invalid input 2: none of those is retried. A
# child that dies takes the host down with exit 1, and the unit restarts
# the pair.
ExecStart=/mnt/storage/scratch/tmp/3LlK4H/case2-unit-jxPM4A/home/mosaic/.local/share/mosaic-stack/copies/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/scripts/mosaic bus start %i
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2 3 4
# `systemctl --user stop` sends SIGTERM to the host, which stops the
# notifier after its poll in flight, then closes the broker.
KillSignal=SIGTERM
KillMode=mixed
TimeoutStopSec=60
NoNewPrivileges=yes
User=mosaic
Group=mosaic
ProtectSystem=strict
ReadWritePaths=/mnt/storage/scratch/tmp/3LlK4H/case2-unit-jxPM4A/home/mosaic
ProtectHome=yes
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
operator: 'match'
stack: |-
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:976:10)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 259.619391
@@ -0,0 +1 @@
5c1dd9b5275023f0a4ad6d75c0d85bfdbea9ceaa7f1ff224b530f6ebf17767c8 packages/cli/systemd/mosaic-proxy.service.in
@@ -0,0 +1,13 @@
diff --git a/packages/cli/systemd/mosaic-proxy.service.in b/packages/cli/systemd/mosaic-proxy.service.in
index d4d67d6b5..71b010867 100644
--- a/packages/cli/systemd/mosaic-proxy.service.in
+++ b/packages/cli/systemd/mosaic-proxy.service.in
@@ -9,7 +9,7 @@ Type=simple
WorkingDirectory=@REPO@
Environment=PATH=@PATH@
@CONFIG_ENV@
-ExecStart=@NODE@ @REPO@/packages/model-proxy/src/run.mjs
+ExecStart=@REPO@/opt/cli-proxy-api
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2 3 4
@@ -0,0 +1,36 @@
# TMPDIR=/mnt/storage/scratch/tmp/JuznlZ HOME=/mnt/storage/scratch/tmp/JuznlZ/home MOSAIC_CONFIG=/mnt/storage/scratch/tmp/JuznlZ/home/.config/mosaic-dev/config.json nice=19 budget=60s
TAP version 13
# Subtest: row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order
not ok 1 - row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order
---
duration_ms: 122.755437
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:966:1'
failureType: 'testCodeFailure'
error: |-
case2-unit: rendered mosaic-proxy.service: ExecStart= must be exactly once in [Service], as ExecStart=/usr/bin/node /mnt/storage/scratch/tmp/JuznlZ/case2-unit-X31DMY/home/mosaic/.local/share/mosaic-stack/copies/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/packages/model-proxy/src/run.mjs
1 !== 0
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 0
actual: 1
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:969:10)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 237.081878
@@ -0,0 +1,68 @@
TMPDIR=/mnt/storage/scratch/tmp/YAGfKJ nice=19
✔ the library install writes the business file, personas and slot dir, makes the copy and starts nothing (386.113555ms)
✔ a library re-run passes and leaves every file as it was (496.225906ms)
✔ a re-run refuses business-file-differs and leaves the file unchanged (376.395513ms)
✔ a re-run refuses persona-differs when an installed persona isn't the staged one, and writes nothing (398.216221ms)
✔ the factory install writes no library mapping, persona or slot dir (247.905208ms)
✔ the factory install refuses a library project and bus.tester off, before anything is written (105.176208ms)
✔ the library install refuses bus.tester on and a persona var in the input (104.503599ms)
✔ the install refuses root, a business id mismatch, another --home and a config dir with group bits (80.144703ms)
✔ the library install needs --personas (7.831929ms)
✔ a validate refusal removes the business file this run wrote (311.701322ms)
✔ N4: a validate refusal on a re-run leaves the matching business file in place, byte for byte (537.848604ms)
✔ a clean library host passes the precheck with no mirror yet (509.009214ms)
✔ the library precheck refuses a persona file at 0644 (506.697677ms)
✔ the library precheck refuses a persona whose digest isn't persona.sha256 (495.113108ms)
✔ the library precheck refuses a persona dir with group bits (481.900638ms)
✔ the library precheck refuses a business file at 0644 (462.626038ms)
✔ the library precheck refuses a changes.jsonl at 0644 (687.490332ms)
✔ the library precheck refuses a Discord binding (499.568905ms)
✔ --library-status refuses library-mirror-missing before the bus has made the mirror (377.717622ms)
✔ --library-status refuses library-never-synced on a fresh mirror (410.956236ms)
✔ --library-status names a recorded lastError before the null lastFetch it leaves (N3) (417.888767ms)
✔ --library-status prints each R-problem's path as an escaped JSON string, then the count, and passes (481.141014ms)
✔ --library-status takes no --profile (6.246241ms)
✔ Vikunja 614: install preserves the explicit notifier decision bytes and mode and creates none when absent (560.431267ms)
✔ row 322 F8: library personas install from the copy with no clone node_modules (183.399079ms)
✔ row 322 F9: importer refusal keeps the error line rather than the Node banner (118.731885ms)
✔ C2-21: library install supplies chief identity and books publish for its real read grant (330.48101ms)
✖ row334 install Next tells root to select proxy only when configured (302.965779ms)
ℹ tests 28
ℹ suites 0
ℹ pass 27
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 10016.247032
✖ failing tests:
test at packages/cli/tests/case2-install.test.mjs:495:1
✖ row334 install Next tells root to select proxy only when configured (302.965779ms)
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /Next: root runs case2-unit.sh[^\n]+add --proxy when the config has a proxy section/. Input:
'case2-install: config /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/config.json\n' +
'case2-install: copy /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc\n' +
'case2-install: personas installed under /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/personas/acme\n' +
'case2-install: business file /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/businesses/acme.json written\n' +
'case2-install: slot dir /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/secrets/acme\n' +
'case2-install: business acme validates\n' +
'case2-install: done. Next: root runs case2-unit.sh --copy cccccccccccccccccccccccccccccccccccccccc --business acme (design 10 step 2c), then 2d and 3.\n' +
'case2-install: Then Jason places the library token in /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/secrets/acme (step 4), and <user> runs case2-precheck.sh --business acme --library-status (step 4a).\n'
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-install.test.mjs:498: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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'case2-install: config /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/config.json\ncase2-install: copy /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc\ncase2-install: personas installed under /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/personas/acme\ncase2-install: business file /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/businesses/acme.json written\ncase2-install: slot dir /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/secrets/acme\ncase2-install: business acme validates\ncase2-install: done. Next: root runs case2-unit.sh --copy cccccccccccccccccccccccccccccccccccccccc --business acme (design 10 step 2c), then 2d and 3.\ncase2-install: Then Jason places the library token in /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/secrets/acme (step 4), and <user> runs case2-precheck.sh --business acme --library-status (step 4a).\n',
expected: /Next: root runs case2-unit.sh[^\n]+add --proxy when the config has a proxy section/,
operator: 'match',
diff: 'simple'
}
@@ -0,0 +1,52 @@
# RED results — sage-4387 (2026-10-25)
Baseline scripts: 78b69e1e6. Current row334 tests. Whole files, sequential.
Both implementation scripts restored via EXIT traps after their run.
No GREEN or mutant run. No old-test failure. No assertion changed.
case2-unit.test.mjs: exit 1; 81 tests, 48 pass, 33 fail, 0 skip.
47 existing tests plus new byte-identical no-proxy test pass.
TMPDIR=/mnt/storage/scratch/tmp/QD3vp5; nice=19; duration 77995.928687 ms.
Every unit failure and its observed reason:
- row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses continuation by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses not-setting by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses RestrictNamespaces by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses PrivateUsers by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses SystemCallFilter by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses PermissionsStartOnly by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses EnvironmentFile by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses AmbientCapabilities by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses CapabilityBoundingSet by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses privileged by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses direct-binary by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses nnp by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses cwd by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses environment by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses install by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses user by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses group by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses ProtectSystem by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses ReadWritePaths by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses ProtectHome by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses PrivateTmp by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy refuses token by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy template missing or symlink refuses: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 rendered ExecStart refuses wrong PIN and missing proxy with P1 status: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 reinstall repins all three and refuses a proxy copy mismatch: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 installed proxy without --proxy refuses and preserves all bytes: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 dangling proxy symlink without flag refuses without deleting: baseline ignores installed proxy symlink without flag, returns 0 instead of refusal 1.
- row334 active proxy reports old copy and all three must restart together: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 failed mosaic-proxy.service start leaves later units unstarted: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 failed [email protected] start leaves later units unstarted: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 installed proxy enforces hardening order and remaining common refusals: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
- row334 proxy installed path rejects symlink and non-regular file: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
case2-install.test.mjs: exit 1; 28 tests, 27 pass, 1 fail, 0 skip.
TMPDIR=/mnt/storage/scratch/tmp/YAGfKJ; nice=19; duration 10016.247032 ms.
- row334 install Next tells root to select proxy only when configured: baseline Next: lacks add --proxy when the config has a proxy section.
Commands used absolute /usr/bin/node --test and absolute test-file paths.
Full plain-text outputs: red-unit.log and red-install.log beside this file.
P2 refusal-code check remains pending row331. Slot returned to Sage.
@@ -0,0 +1,873 @@
TMPDIR=/mnt/storage/scratch/tmp/QD3vp5 nice=19
✔ row253 factory socket uses actual config and target-uid query without widening hardening (351.144059ms)
✔ row253 installer refuses unsafe location and query facts before unit writes (3621.94316ms)
✔ row253 query timeout kills its process group and leaves no running child (7054.318085ms)
✔ row253 protected homes refuse before any filesystem access (9.291102ms)
✔ row253 C4 sleep query has exact inner reason and cleans all members (10796.380077ms)
✔ row253 C4 signals query has exact inner reason and cleans all members (10819.227773ms)
✔ row253 C4 member query has exact inner reason and cleans all members (10897.328548ms)
✔ row253 C4 overflow is output-limit in both sudo modes (554.12811ms)
✔ row253 C4 stalled supervisor uses the outer reason and explicit residual cleanup (14201.797743ms)
✔ row253 C4 inline supervisor is byte-identical in both host scripts (0.888234ms)
✔ row253 C4 cleanup budget starts after inventory pass one (46.615614ms)
✔ row253 query reaps its group even when a child closes stdout before the query returns (152.118314ms)
✔ row253 config parser is strict (11.560624ms)
✔ a clean library render writes both units 0644 with the fixed lines, and enables both then starts the bus then the Console (120.676804ms)
✔ PATH starts with the --node directory, for a node outside the default list and for /usr/bin/node (131.630623ms)
✔ a failed bus start refuses with the Console not started (123.177864ms)
✔ the script never links a unit file (0.18683ms)
✔ factory renders --pm once and one MOSAIC_CLAUDE_BIN line, and without them both tokens render empty (746.075081ms)
✔ a re-install over running units says they are still on the old copy and must restart together, never that it started them (494.63679ms)
✔ a re-install to another copy re-renders both units, and an edited installed unit refuses (1175.719345ms)
✔ a template with a namespace, user or system call restriction refuses (335.625113ms)
✔ the bus template's EnvironmentFile line must be there and point at the session env, and the Console template must have none (214.600182ms)
✔ a template holding an unknown token, PermissionsStartOnly or a privileged Exec prefix refuses (151.081813ms)
✔ a symlink at either unit path refuses (203.555513ms)
✔ a --node that is relative, not root-owned or under <home> refuses (27.297759ms)
✔ a re-install with a different --node or different serve flags refuses (599.873393ms)
✔ an installed pair whose units name different copies refuses (328.321083ms)
✔ a copy id that is short, uppercase, holds / or .., or names no directory refuses (46.028703ms)
✔ library refuses a privileged-floor miss, a bad proof pair and every flag outside its set (344.833107ms)
✔ library refuses a proof file not owned by <user>'s uid (18.013172ms)
✔ factory refuses duplicate socket, a missing --port or --business, and each proxy flag (209.757808ms)
✔ row253 factory refuses retired TCP and a value after the socket marker (59.218628ms)
✔ factory refuses a --claude that is relative, not root-owned or under <home>, and a re-install with another --pm or --claude (1686.680735ms)
✔ the precheck passes on a good host and prints free memory, without the key (226.963557ms)
✔ the precheck refuses each failed host check (1765.731095ms)
✔ the precheck refuses a user missing from passwd, a business file that does not load, and a symlinked run root (927.260864ms)
✔ a bwrap that fails refuses and prints the sysctl and where bwrap is (200.731617ms)
✔ an unprivileged port floor at or below the Console's port refuses, and so does a missing Console unit (1055.360802ms)
✔ bus.tester: library refuses it on, factory needs it on, and factory refuses a library business (927.574539ms)
✔ the claude check runs only for a claude-code instance, and refuses a claude that appends or answers wrong (1694.074713ms)
✔ the claude check runs the bus unit's MOSAIC_CLAUDE_BIN over claude on PATH (498.707509ms)
✔ the session env file refuses when missing, open to others, a symlink, empty, or emptied by a later line (1155.959127ms)
✔ inside a session's sandbox a bind below 1024 fails with EACCES, and a bind on a free high port succeeds (106.466066ms)
✔ Vikunja 614 factory: scoped @anthropic-ai Claude path renders and passes installed read-back (1024.721235ms)
✔ Vikunja 614 factory: plain paths still refuse every named unsafe character (117.946806ms)
✔ Vikunja 614 precheck: explicit null and named notifier decisions pass (384.090317ms)
✔ Vikunja 614 precheck: notifier config refuses missing, public, malformed and extra-key decisions (1596.303308ms)
✔ row334 no-proxy renders stay byte-identical to the pre-row renderer (316.417263ms)
✖ row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order (6.436602ms)
✖ row334 proxy refuses continuation by message before writing (6.291734ms)
✖ row334 proxy refuses not-setting by message before writing (5.588449ms)
✖ row334 proxy refuses RestrictNamespaces by message before writing (5.71389ms)
✖ row334 proxy refuses PrivateUsers by message before writing (5.34285ms)
✖ row334 proxy refuses SystemCallFilter by message before writing (5.400994ms)
✖ row334 proxy refuses PermissionsStartOnly by message before writing (5.443023ms)
✖ row334 proxy refuses EnvironmentFile by message before writing (5.052075ms)
✖ row334 proxy refuses AmbientCapabilities by message before writing (5.027453ms)
✖ row334 proxy refuses CapabilityBoundingSet by message before writing (5.604853ms)
✖ row334 proxy refuses privileged by message before writing (5.176325ms)
✖ row334 proxy refuses direct-binary by message before writing (5.301058ms)
✖ row334 proxy refuses nnp by message before writing (4.978268ms)
✖ row334 proxy refuses cwd by message before writing (4.903056ms)
✖ row334 proxy refuses environment by message before writing (4.928207ms)
✖ row334 proxy refuses install by message before writing (4.935225ms)
✖ row334 proxy refuses user by message before writing (4.996881ms)
✖ row334 proxy refuses group by message before writing (4.740792ms)
✖ row334 proxy refuses ProtectSystem by message before writing (7.412574ms)
✖ row334 proxy refuses ReadWritePaths by message before writing (5.039647ms)
✖ row334 proxy refuses ProtectHome by message before writing (4.948113ms)
✖ row334 proxy refuses PrivateTmp by message before writing (5.049564ms)
✖ row334 proxy refuses token by message before writing (5.291145ms)
✖ row334 proxy template missing or symlink refuses (4.77612ms)
✖ row334 rendered ExecStart refuses wrong PIN and missing proxy with P1 status (5.907949ms)
✖ row334 reinstall repins all three and refuses a proxy copy mismatch (5.706372ms)
✖ row334 installed proxy without --proxy refuses and preserves all bytes (4.919106ms)
✖ row334 dangling proxy symlink without flag refuses without deleting (127.81539ms)
✖ row334 active proxy reports old copy and all three must restart together (5.181396ms)
✖ row334 failed mosaic-proxy.service start leaves later units unstarted (4.827066ms)
✖ row334 failed [email protected] start leaves later units unstarted (4.558348ms)
✖ row334 installed proxy enforces hardening order and remaining common refusals (5.170683ms)
✖ row334 proxy installed path rejects symlink and non-regular file (4.685001ms)
ℹ tests 81
ℹ suites 0
ℹ pass 48
ℹ fail 33
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 77995.928687
✖ failing tests:
test at packages/cli/tests/case2-unit.test.mjs:966:1
✖ row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order (6.436602ms)
AssertionError [ERR_ASSERTION]: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:969: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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses continuation by message before writing (6.291734ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses not-setting by message before writing (5.588449ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses RestrictNamespaces by message before writing (5.71389ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses PrivateUsers by message before writing (5.34285ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses SystemCallFilter by message before writing (5.400994ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses PermissionsStartOnly by message before writing (5.443023ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses EnvironmentFile by message before writing (5.052075ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses AmbientCapabilities by message before writing (5.027453ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses CapabilityBoundingSet by message before writing (5.604853ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses privileged by message before writing (5.176325ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses direct-binary by message before writing (5.301058ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses nnp by message before writing (4.978268ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses cwd by message before writing (4.903056ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses environment by message before writing (4.928207ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses install by message before writing (4.935225ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses user by message before writing (4.996881ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses group by message before writing (4.740792ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses ProtectSystem by message before writing (7.412574ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses ReadWritePaths by message before writing (5.039647ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses ProtectHome by message before writing (4.948113ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses PrivateTmp by message before writing (5.049564ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1004:52
✖ row334 proxy refuses token by message before writing (5.291145ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1012:1
✖ row334 proxy template missing or symlink refuses (4.77612ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1014:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1019:1
✖ row334 rendered ExecStart refuses wrong PIN and missing proxy with P1 status (5.907949ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
4 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1021: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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 4,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1044:1
✖ row334 reinstall repins all three and refuses a proxy copy mismatch (5.706372ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
4 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1046: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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 4,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1055:1
✖ row334 installed proxy without --proxy refuses and preserves all bytes (4.919106ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
4 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1057: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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 4,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1065:1
✖ row334 dangling proxy symlink without flag refuses without deleting (127.81539ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 0: case2-unit: wrote /mnt/storage/scratch/tmp/QD3vp5/case2-unit-h4V4Zn/units/[email protected] and /mnt/storage/scratch/tmp/QD3vp5/case2-unit-h4V4Zn/units/mosaic-webui.service from copy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; enabled and started [email protected], then mosaic-webui.service
0 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1068:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1072:1
✖ row334 active proxy reports old copy and all three must restart together (5.181396ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
4 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1074: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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 4,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1080:73
✖ row334 failed mosaic-proxy.service start leaves later units unstarted (4.827066ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1082:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1080:73
✖ row334 failed [email protected] start leaves later units unstarted (4.558348ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1082:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1088:1
✖ row334 installed proxy enforces hardening order and remaining common refusals (5.170683ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
4 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1090: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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 4,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/cli/tests/case2-unit.test.mjs:1103:1
✖ row334 proxy installed path rejects symlink and non-regular file (4.685001ms)
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
4 !== 1
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1106:3)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1 @@
No node --test processes found at 2026-10-26T22:42:35Z (ps search rc1).
@@ -0,0 +1,6 @@
scripts/case2-unit.sh: OK
scripts/case2-install.sh: OK
packages/cli/tests/case2-unit.test.mjs: OK
packages/cli/tests/case2-install.test.mjs: OK
packages/cli/systemd/mosaic-proxy.service.in: OK
packages/model-proxy/PIN: OK
@@ -0,0 +1,23 @@
# TMPDIR=/mnt/storage/scratch/tmp/ayiWpX HOME=/mnt/storage/scratch/tmp/ayiWpX/home MOSAIC_CONFIG=/mnt/storage/scratch/tmp/ayiWpX/home/.config/mosaic-dev/config.json nice=19 budget=60s
TAP version 13
# Subtest: row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order
ok 1 - row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order
---
duration_ms: 143.930692
type: 'test'
...
# Subtest: row334 rendered ExecStart refuses wrong PIN and missing proxy with P1 status
ok 2 - row334 rendered ExecStart refuses wrong PIN and missing proxy with P1 status
---
duration_ms: 308.811879
type: 'test'
...
1..2
# tests 2
# suites 0
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 569.147722
+4
View File
@@ -1893,3 +1893,7 @@ are never rewritten or removed; corrections are new entries.
2026-10-29T05:49:53Z | Codex Daybreak Blue xhigh implementation sub-agent | Mos2255 #385 review repair R2 on work/vikunja-19-385-q284-complete at unchanged HEAD 9936a1be1a37fdb30ba53cf2f14b7897225c3e4a/base 09a847edaa25b609bac7728ff83f224708a29399 | Corrects the 03:27:31Z historical blocker: pinned oauth4webapi3.8.8 fixture is now present without install and original prep is committed. F1/F2/F3 repaired in uncommitted source/tests/docs; F4/F7 disclosed only, F6 append conflicts preserved. Three focused test files only: intentional RED captured, GREEN controls plus T53 12/12, T55 8/8, T57 13/13 and T58 11/11 pass. No root/native/live/full-suite/CI, commit/push/PR/API/merge, frozen packet or live-pin action.
2026-11-02T18:03:53Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R3 on work/vikunja-19-385-q284-complete at ab2bc23c7 | R2-F1 fixed: stack-upgrade refuses a library args file holding `--send` after `--` before any unit stops, for upgrade and rollback; R2-I1 component case and R2-I2 TOOLS.md precision added, R2-I3 noted. RED 0/1 then GREEN 2/2; stack-upgrade 23/23, stack-release 225/225, case2-unit 48/48. One local commit; no push, PR, Vikunja, Gitea or live action.
2026-11-02T18:19:04Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R4 on work/vikunja-19-385-q284-complete at d0ddfa8de | R3-F1 fixed: stack-upgrade refuses `console-unit-legacy` before any unit stops when a library host's installed Console unit runs serve with `--send`, for upgrade and rollback; host-run-sheet section 7 and TOOLS.md updated. RED 24/25 then GREEN 25/25 in stack-upgrade.test.mjs; other suites not rerun (no changed file read). One local commit; no push, PR, Vikunja, Gitea or live action.
2026-10-25 — rockerduck — row334 (#1832), Case 2 proxy system unit — design/read phase; tests held for Sage CLEAR; queue start lock diagnosed.
2026-10-25 — rockerduck — row334 (#1832), Case 2 proxy system unit — queue start rev4079; tests then implementation written, design accepted by Sage; verification held for CLEAR, P2 refusal-code check pending row331.
2026-10-25 — rockerduck — row334 RED-only slot sage-4387 — both whole files against pre-change scripts: 74 older tests pass, 34 new expected failures; implementations restored, slot returned; GREEN/mutants held.
2026-10-26 — rockerduck — row334 Mos1961 targeted serial slot — integrated 09a847eda, whole files 81+28 pass, two mutants killed/restored 2 pass; old RED preserved, test roots removed, SLOTBACK; no push/live action/review dispatch.
@@ -490,3 +490,10 @@ test('C2-21: library install supplies chief identity and books publish for its r
assert.ok(doc.roles.chief.identity?.gitea?.user);
assert.notEqual(doc.roles.chief.identity?.gitea?.user, 'writer');
});
test('row334 install Next tells root to select proxy only when configured', t => {
const x = world(t), r = install(x);
assert.equal(r.code, 0, r.out);
assert.match(r.out, /Next: root runs case2-unit.sh[^\n]+add --proxy when the config has a proxy section/);
});
+170 -2
View File
@@ -7,10 +7,10 @@ import { test } from "node:test";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { createServer } from "node:net";
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
import { chmodSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { businessDoc, writeJson } from "../../business/tests/helpers.mjs";
import { businessDoc, systemConfig, writeJson } from "../../business/tests/helpers.mjs";
import { binds, prepareRun, sandboxArgv, sandboxProblem } from "../../seat/src/sandbox.mjs";
import { tmp } from "./helpers.mjs";
import { CASE_SUDO_SESSION, recordedLivePids, killRecordedPids, fixtureGuard, cleanupFixture, pollFixtureGone, blockingQuerySource } from "./case-sudo-fixture.mjs";
@@ -950,3 +950,171 @@ test("Vikunja 614 precheck: notifier config refuses missing, public, malformed a
assert.match(r.out, /REFUSED notifier config:/, `${fault}: ${r.out}`);
}
});
// Row334: render only into the scratch hub; fake systemctl never touches the host.
const PROXY_TEMPLATE = join(REPO, 'packages/cli/systemd/mosaic-proxy.service.in');
const proxyFile = h => join(h.units, 'mosaic-proxy.service');
const proxyUnit = (h, options = {}) => unit(h, { ...options, extra: ['--proxy', ...(options.extra ?? [])] });
test('row334 no-proxy renders stay byte-identical to the pre-row renderer', t => {
const h = hub(t);
// Immutable committed baseline, executed only with the same scratch seams.
const baseline = spawnSync('git', ['show', '78b69e1e6:scripts/case2-unit.sh'], { cwd: REPO, encoding: 'utf8' });
assert.equal(baseline.status, 0, baseline.stderr);
const old = script(join(h.root, 'baseline/scripts/case2-unit.sh'), baseline.stdout);
const args = ['--user', 'mosaic', '--home', h.home, '--business', 'acme', '--copy', COPY_A, '--node', USR_NODE, '--', ...libraryFlags(h)];
const r = spawnSync(old, args, { encoding: 'utf8', env: { PATH: `${h.bin}:${process.env.PATH}`, CASE2_UNIT_DIR: h.units,
CASE2_SYSTEMCTL: join(h.bin, 'systemctl'), CASE2_GETENT: join(h.bin, 'getent'), CASE2_BUS_TEMPLATE: BUS_TEMPLATE, CASE2_WEBUI_TEMPLATE: WEBUI_TEMPLATE } });
assert.equal(r.status, 0, r.stderr);
const before = [read(busFile(h)), read(webuiFile(h))];
assert.equal(unit(h).code, 0);
assert.deepEqual([read(busFile(h)), read(webuiFile(h))], before);
assert.equal(existsSync(proxyFile(h)), false);
});
test('row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order', t => {
const h = hub(t);
const r = proxyUnit(h, { profile: 'factory', flags: ['--port', '443', '--business', 'acme'], extra: ['--claude', TRUE_BIN] });
assert.equal(r.code, 0, r.err);
const proxy = read(proxyFile(h));
const expected = read(PROXY_TEMPLATE).replaceAll('@REPO@', copyDir(h)).replaceAll('@PATH@', '/usr/bin:/usr/local/bin:/bin')
.replaceAll('@NODE@', USR_NODE).replace(/^@CONFIG_ENV@\n/m, '').replace('WantedBy=default.target', 'WantedBy=multi-user.target')
.replace('TimeoutStopSec=60', `TimeoutStopSec=60\nUser=mosaic\nGroup=mosaic\n${HARDENING(h).join('\n')}`);
assert.equal(proxy, expected);
assert.equal(statSync(proxyFile(h)).mode & 0o777, 0o644);
assert.match(read(busFile(h)), /Description=[^\n]+\nWants=mosaic-proxy.service\nAfter=mosaic-proxy.service\n/);
assert.deepEqual(lines(proxy, 'ExecStart'), [`${USR_NODE} ${copyDir(h)}/packages/model-proxy/src/run.mjs`]);
assert.doesNotMatch(proxy, /EnvironmentFile=|Capabilities=|CapabilityBoundingSet=|MOSAIC_CLAUDE_BIN/);
assert.doesNotMatch(read(webuiFile(h)), /Wants=|After=/);
assert.deepEqual(read(h.log).trim().split('\n'), [
'is-active --quiet mosaic-proxy.service', 'is-active --quiet [email protected]', 'is-active --quiet mosaic-webui.service',
'daemon-reload', 'enable mosaic-proxy.service [email protected] mosaic-webui.service',
'start mosaic-proxy.service', 'start [email protected]', 'start mosaic-webui.service',
]);
});
const proxyRefusals = [
['continuation', s => s.replace('Type=simple', 'Type=simple' + String.fromCharCode(92)), /a line continues onto the next/],
['not-setting', s => s.replace('Type=simple', 'not a setting'), /neither a section/],
...['RestrictNamespaces', 'PrivateUsers', 'SystemCallFilter', 'PermissionsStartOnly', 'EnvironmentFile', 'AmbientCapabilities', 'CapabilityBoundingSet'].map(k =>
[k, s => s.replace('Type=simple', `Type=simple\n${k}=yes`), new RegExp(`refusing ${k}=`)]),
['privileged', s => s.replace('ExecStart=', 'ExecStart=+'), /Exec line that runs with full privileges/],
['direct-binary', s => s.replace('packages/model-proxy/src/run.mjs', 'opt/cli-proxy-api'), /ExecStart= must be exactly once/],
['nnp', s => s.replace('NoNewPrivileges=yes', 'NoNewPrivileges=no'), /NoNewPrivileges= must be exactly once/],
['cwd', s => s.replace('WorkingDirectory=@REPO@', 'WorkingDirectory=/elsewhere'), /WorkingDirectory= must be exactly once/],
['environment', s => s.replace('Environment=PATH=@PATH@', 'Environment=PATH=/elsewhere'), /Environment= lines must be exactly/],
['install', s => s.replace('WantedBy=default.target', 'WantedBy=other.target'), /WantedBy= must be exactly once/],
['user', s => s.replace('Type=simple', 'Type=simple\nUser=other'), /User= must be exactly once/],
['group', s => s.replace('Type=simple', 'Type=simple\nGroup=other'), /Group= must be exactly once/],
...['ProtectSystem', 'ReadWritePaths', 'ProtectHome', 'PrivateTmp'].map(k =>
[k, s => s.replace('Type=simple', `Type=simple\n${k}=other`), new RegExp(`${k}= must be exactly once`)]),
['token', s => s.replace('Type=simple', 'Type=simple\nX-Note=@UNKNOWN@'), /a token is left after the render/],
];
for (const [name, edit, message] of proxyRefusals) test(`row334 proxy refuses ${name} by message before writing`, t => {
const h = hub(t);
const file = template(h, PROXY_TEMPLATE, `${name}.in`, edit);
refuses(proxyUnit(h, { env: { CASE2_PROXY_TEMPLATE: file } }), message);
assert.deepEqual(readdirSync(h.units), []);
assert.equal(existsSync(h.log), false);
});
test('row334 proxy template missing or symlink refuses', t => {
const h = hub(t), missing = join(h.root, 'missing.in');
refuses(proxyUnit(h, { env: { CASE2_PROXY_TEMPLATE: missing } }), /template missing or not a regular file/);
symlinkSync(PROXY_TEMPLATE, missing);
refuses(proxyUnit(h, { env: { CASE2_PROXY_TEMPLATE: missing } }), /template missing or not a regular file/);
});
test('row334 rendered ExecStart refuses wrong PIN and missing proxy with P1 status', t => {
const h = hub(t);
assert.equal(proxyUnit(h).code, 0);
const copy = copyDir(h);
cpSync(join(REPO, 'packages/model-proxy'), join(copy, 'packages/model-proxy'), { recursive: true });
cpSync(join(REPO, 'scripts/mosaic-config.mjs'), join(copy, 'scripts/mosaic-config.mjs'));
const config = systemConfig(join(h.root, 'runner'));
const doc = JSON.parse(read(config));
mkdirSync(doc.dataRoot, { recursive: true, mode: 0o700 });
const pin = JSON.parse(read(join(REPO, 'packages/model-proxy/PIN')));
const binary = join(doc.dataRoot, 'opt/cliproxyapi', pin.version, 'cli-proxy-api');
script(binary, `echo unexpected-spawn > '${h.root}/spawned'`);
writeJson(join(dirname(config), 'proxy.json'), { port: 18541, binary });
const argv = lines(read(proxyFile(h)), 'ExecStart')[0].split(' ');
const run = () => spawnSync(argv[0], argv.slice(1), { encoding: 'utf8', env: { PATH: '/usr/bin:/bin', HOME: h.home, MOSAIC_CONFIG: config } });
const bad = run();
assert.equal(bad.status, 3, bad.stderr);
assert.equal(bad.stderr, 'proxy-install-invalid\n');
assert.equal(existsSync(join(h.root, 'spawned')), false);
rmSync(join(dirname(config), 'proxy.json'));
const absent = run();
assert.equal(absent.status, 3, absent.stderr);
assert.equal(absent.stderr, 'proxy-not-configured\n');
});
test('row334 reinstall repins all three and refuses a proxy copy mismatch', t => {
const h = hub(t);
assert.equal(proxyUnit(h).code, 0);
assert.equal(proxyUnit(h, { copy: COPY_B }).code, 0);
for (const file of [proxyFile(h), busFile(h), webuiFile(h)]) assert.match(read(file), new RegExp(copyDir(h, COPY_B)));
writeFileSync(proxyFile(h), read(proxyFile(h)).replaceAll(copyDir(h, COPY_B), copyDir(h)));
const before = [proxyFile(h), busFile(h), webuiFile(h)].map(read);
refuses(proxyUnit(h), /installed units name different copies/);
assert.deepEqual([proxyFile(h), busFile(h), webuiFile(h)].map(read), before);
});
test('row334 installed proxy without --proxy refuses and preserves all bytes', t => {
const h = hub(t);
assert.equal(proxyUnit(h).code, 0);
const files = [proxyFile(h), busFile(h), webuiFile(h)], before = files.map(read);
writeFileSync(h.log, '');
refuses(unit(h), /installed mosaic-proxy.service requires --proxy/);
assert.deepEqual(files.map(read), before);
assert.equal(read(h.log), '');
});
test('row334 dangling proxy symlink without flag refuses without deleting', t => {
const h = hub(t);
symlinkSync(join(h.root, 'absent'), proxyFile(h));
refuses(unit(h), /installed mosaic-proxy.service requires --proxy/);
assert.equal(readdirSync(h.units).includes('mosaic-proxy.service'), true);
});
test('row334 active proxy reports old copy and all three must restart together', t => {
const h = hub(t);
assert.equal(proxyUnit(h).code, 0);
const r = proxyUnit(h, { copy: COPY_B, env: { CASE2_TEST_ACTIVE: 'mosaic-proxy.service [email protected] mosaic-webui.service' } });
assert.equal(r.code, 0, r.err);
assert.match(r.out, /mosaic-proxy.service was already running/);
assert.match(r.out, /restart mosaic-proxy.service, [email protected] and mosaic-webui.service together/);
});
for (const name of ['mosaic-proxy.service', '[email protected]']) test(`row334 failed ${name} start leaves later units unstarted`, t => {
const h = hub(t);
refuses(proxyUnit(h, { env: { CASE2_TEST_FAIL: `start ${name}` } }), /systemctl start .* failed/);
assert.doesNotMatch(read(h.log), /start mosaic-webui.service/);
if (name === 'mosaic-proxy.service') assert.doesNotMatch(read(h.log), /start [email protected]/);
});
test('row334 installed proxy enforces hardening order and remaining common refusals', t => {
const h = hub(t);
assert.equal(proxyUnit(h).code, 0);
const original = read(proxyFile(h));
for (const [edit, message] of [
[s => s.replace('ProtectSystem=strict\nReadWritePaths=' + h.home, 'ReadWritePaths=' + h.home + '\nProtectSystem=strict'), /hardening lines must be in the order/],
[s => s.replace('WorkingDirectory=' + copyDir(h), 'WorkingDirectory=%h'), /names no copy/],
[s => s.replace('Type=simple', 'Type=simple\nX-Note=%h'), /%h is left; a system unit/],
[s => s.replace('RestartSec=15', 'RestartSec=16'), /differs from the render in more than the copy id/],
]) {
writeFileSync(proxyFile(h), edit(original));
refuses(proxyUnit(h), message);
}
});
test('row334 proxy installed path rejects symlink and non-regular file', t => {
const h = hub(t);
symlinkSync(join(h.root, 'absent'), proxyFile(h));
refuses(proxyUnit(h), /is a symlink/);
rmSync(proxyFile(h));
mkdirSync(proxyFile(h));
refuses(proxyUnit(h), /is not a regular file/);
});
+1 -1
View File
@@ -228,7 +228,7 @@ fi
say "business $BUSINESS validates"
# 8. What comes next. Root's steps run from root's own checkout.
say "done. Next: root runs case2-unit.sh --copy $COMMIT --business $BUSINESS (design 10 step 2c), then 2d and 3."
say "done. Next: root runs case2-unit.sh --copy $COMMIT --business $BUSINESS (add --proxy when the config has a proxy section; design 10 step 2c), then 2d and 3."
if [ "$PROFILE" = library ]; then
say "Then Jason places the library token in $SLOT (step 4), and <user> runs case2-precheck.sh --business $BUSINESS --library-status (step 4a)."
fi
+65 -22
View File
@@ -1,11 +1,11 @@
#!/usr/bin/env bash
# Case 2 row C2-13a (design 9.3, 10 step 2c): render and install the two
# system units a hub runs as, [email protected] (enabled as
# system units a hub runs as (plus P1's proxy with --proxy), [email protected] (enabled as
# mosaic-bus@<business>.service) and mosaic-webui.service, from the existing
# templates, which it doesn't change.
#
# case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH
# [--profile factory|library] [--pm] [--claude PATH]
# [--profile factory|library] [--proxy] [--pm] [--claude PATH]
# -- SERVE FLAGS
#
# Root runs it from a root-owned checkout of the reviewed commit outside
@@ -33,8 +33,8 @@
# --pm and --claude.
#
# On a re-install the installed units must equal the render in everything
# but the copy id, and both must name one copy, so a re-pin or rollback
# re-renders the pair together. A unit that was already running stays on its
# but the copy id, and all selected units must name one copy, so a re-pin or rollback
# re-renders them together. A unit that was already running stays on its
# old copy, since `systemctl start` leaves it alone; the script says so and
# that the bus and Console must restart together, and restarts nothing.
#
@@ -43,6 +43,7 @@
# the root and root-owned checks apply only there
# CASE2_SYSTEMCTL the systemctl command, default systemctl
# CASE2_GETENT the getent command, default getent
# CASE2_PROXY_TEMPLATE default packages/cli/systemd/mosaic-proxy.service.in
# CASE2_BUS_TEMPLATE default packages/cli/systemd/mosaic-bus.service.in
# CASE2_WEBUI_TEMPLATE default packages/webui/systemd/mosaic-webui.service.in
#
@@ -55,10 +56,12 @@ SYSTEMCTL="${CASE2_SYSTEMCTL:-systemctl}"
GETENT="${CASE2_GETENT:-getent}"
BUS_TEMPLATE="${CASE2_BUS_TEMPLATE:-$REPO/packages/cli/systemd/mosaic-bus.service.in}"
WEBUI_TEMPLATE="${CASE2_WEBUI_TEMPLATE:-$REPO/packages/webui/systemd/mosaic-webui.service.in}"
PROXY_TEMPLATE="${CASE2_PROXY_TEMPLATE:-$REPO/packages/cli/systemd/mosaic-proxy.service.in}"
PROXY_UNIT=mosaic-proxy.service
BUS_UNIT=[email protected]
WEBUI_UNIT=mosaic-webui.service
usage() { echo 'usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS' >&2; exit 4; }
usage() { echo 'usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--proxy] [--pm] [--claude PATH] -- SERVE FLAGS' >&2; exit 4; }
die() { echo "case2-unit: $*" >&2; exit 1; }
# A value the unit carries as itself: no space, %, $, quote, backslash, & or |.
plain() { case "$1" in *[!A-Za-z0-9/._+@-]*) return 1 ;; esac; }
@@ -69,7 +72,7 @@ abs_plain() {
[ "$1" = / ] || [ "${1%/}" = "$1" ] || die "$2 must not end in /: $1"
}
PROFILE=library USER_NAME='' HOME_DIR='' BUSINESS='' COPY_ID='' NODE='' PM=0 CLAUDE='' CLAUDE_SET=0
PROFILE=library USER_NAME='' HOME_DIR='' BUSINESS='' COPY_ID='' NODE='' PROXY=0 PM=0 CLAUDE='' CLAUDE_SET=0
while [ $# -gt 0 ]; do
case "$1" in
--profile|--user|--home|--business|--copy|--node|--claude)
@@ -80,6 +83,7 @@ while [ $# -gt 0 ]; do
--claude) CLAUDE="$2"; CLAUDE_SET=1 ;;
esac
shift 2 ;;
--proxy) PROXY=1; shift ;;
--pm) PM=1; shift ;;
--) shift; break ;;
*) usage ;;
@@ -152,6 +156,7 @@ fi
# The checkout the templates come from, when installing for real.
if [ "$UNIT_DIR" = "$DEFAULT_UNIT_DIR" ]; then
case "$REPO/" in "$HOME_DIR"/*) die "run this from a root-owned checkout outside $HOME_DIR, not $REPO" ;; esac
[ "$PROXY" = 0 ] || root_only "$PROXY_TEMPLATE" 'proxy template'
root_only "$BUS_TEMPLATE" 'bus template'
root_only "$WEBUI_TEMPLATE" 'Console template'
fi
@@ -453,7 +458,10 @@ render() {
fi
if printf '%s\n' "$text" | grep -q '%h'; then die "$template: %h is left after the render"; fi
local add="User=$USER_NAME\nGroup=$GROUP\n${HARDENING//$'\n'/\\n}\n"
[ "$kind" = bus ] || add="$add${CAPS//$'\n'/\\n}\n"
[ "$kind" != webui ] || add="$add${CAPS//$'\n'/\\n}\n"
if [ "$kind" = bus ] && [ "$PROXY" = 1 ]; then
text="$(printf '%s\n' "$text" | sed "/^Description=/a Wants=$PROXY_UNIT\\nAfter=$PROXY_UNIT")"
fi
# The added lines go at the end of [Service], before its trailing blank
# lines. In [Install], WantedBy=multi-user.target replaces
# WantedBy=default.target in both units, so both start at boot (Q189).
@@ -521,7 +529,7 @@ check_unit() {
one WorkingDirectory "$copy"
# Environment= lines: PATH, and MOSAIC_CLAUDE_BIN only with --claude.
local envs want_envs="[Service] PATH=$UNIT_PATH"
[ "$kind" = webui ] || [ -z "$CLAUDE_ENV" ] || want_envs="$want_envs
[ "$kind" != bus ] || [ -z "$CLAUDE_ENV" ] || want_envs="$want_envs
[Service] MOSAIC_CLAUDE_BIN=$CLAUDE"
envs="$(printf '%s\n' "$s" | awk -F'\t' '$2 == "Environment" { print $1 "\t" $3 }')"
[ "$envs" = "$want_envs" ] || die "$what: the Environment= lines must be exactly: $(printf '%s' "$want_envs" | cut -f2 | tr '\n' ' ')"
@@ -541,6 +549,11 @@ check_unit() {
one EnvironmentFile "$ENV_FILE"
none AmbientCapabilities
none CapabilityBoundingSet
elif [ "$kind" = proxy ]; then
one ExecStart "$NODE $copy/packages/model-proxy/src/run.mjs"
none EnvironmentFile
none AmbientCapabilities
none CapabilityBoundingSet
else
one ExecStart "$NODE $copy/packages/webui/src/cli.mjs serve $FLAGS"
none EnvironmentFile
@@ -558,7 +571,16 @@ check_unit webui "$WEBUI_TEXT" "rendered $WEBUI_UNIT" "$COPY"
# copy, and each equals the render once its copy id is the new one.
[ -d "$UNIT_DIR" ] && [ ! -L "$UNIT_DIR" ] || die "no unit directory $UNIT_DIR"
installed_ids=''
for pair in "bus:$BUS_UNIT:BUS_TEXT" "webui:$WEBUI_UNIT:WEBUI_TEXT"; do
PAIRS=("bus:$BUS_UNIT:BUS_TEXT" "webui:$WEBUI_UNIT:WEBUI_TEXT")
if [ "$PROXY" = 1 ]; then
PROXY_TEXT="$(render proxy "$PROXY_TEMPLATE")" || exit 1
check_unit proxy "$PROXY_TEXT" "rendered $PROXY_UNIT" "$COPY"
PAIRS=("proxy:$PROXY_UNIT:PROXY_TEXT" "${PAIRS[@]}")
else
[ ! -e "$UNIT_DIR/$PROXY_UNIT" ] && [ ! -L "$UNIT_DIR/$PROXY_UNIT" ] \
|| die "installed $PROXY_UNIT requires --proxy; nothing was changed"
fi
for pair in "${PAIRS[@]}"; do
IFS=: read -r kind name var <<<"$pair"
path="$UNIT_DIR/$name"
[ ! -L "$path" ] || die "refusing: $path is a symlink"
@@ -573,13 +595,17 @@ for pair in "bus:$BUS_UNIT:BUS_TEXT" "webui:$WEBUI_UNIT:WEBUI_TEXT"; do
installed_ids="$installed_ids $old"
done
read -r -a ids <<<"$installed_ids"
[ "${#ids[@]}" -lt 2 ] || [ "${ids[0]}" = "${ids[1]}" ] || die "the installed units name different copies, ${ids[0]} and ${ids[1]}"
for old in "${ids[@]}"; do
[ "$old" = "${ids[0]}" ] || die "the installed units name different copies, ${ids[0]} and $old"
done
# `systemctl start` leaves a running unit on the copy it started from, so
# note which units run before the write (row 195 owns the restart).
BUS_NAME="mosaic-bus@$BUSINESS.service"
NAMES=("$BUS_NAME" "$WEBUI_UNIT")
[ "$PROXY" = 0 ] || NAMES=("$PROXY_UNIT" "${NAMES[@]}")
running=''
for name in "$BUS_NAME" "$WEBUI_UNIT"; do
for name in "${NAMES[@]}"; do
if "$SYSTEMCTL" is-active --quiet "$name"; then running="$running $name"; fi
done
@@ -592,23 +618,40 @@ write_unit() {
[ "$(id -u)" != 0 ] || chown root:root "$tmp"
mv -fT -- "$tmp" "$UNIT_DIR/$name"
}
write_unit "$BUS_UNIT" "$BUS_TEXT"
write_unit "$WEBUI_UNIT" "$WEBUI_TEXT"
# Enable both, then start the bus, then the Console (design 10 step 2c).
# serve calls the bus per request, so the units carry no After= or Wants=.
for pair in "${PAIRS[@]}"; do
IFS=: read -r kind name var <<<"$pair"
write_unit "$name" "${!var}"
done
# Enable all selected units before starting proxy (if selected), bus, Console.
# Only the bus depends on the proxy; the Console calls the bus per request.
"$SYSTEMCTL" daemon-reload || die "systemctl daemon-reload failed; the units are written, nothing is enabled or started"
"$SYSTEMCTL" enable "$BUS_NAME" "$WEBUI_UNIT" || die "systemctl enable $BUS_NAME $WEBUI_UNIT failed; nothing is started"
"$SYSTEMCTL" enable "${NAMES[@]}" || die "systemctl enable ${NAMES[*]} failed; nothing is started"
if [ "$PROXY" = 1 ]; then
"$SYSTEMCTL" start "$PROXY_UNIT" || die "systemctl start $PROXY_UNIT failed; the bus and Console are not started"
fi
"$SYSTEMCTL" start "$BUS_NAME" || die "systemctl start $BUS_NAME failed; the Console is not started"
"$SYSTEMCTL" start "$WEBUI_UNIT" || die "systemctl start $WEBUI_UNIT failed; the bus is running"
if [ -z "$running" ]; then
echo "case2-unit: wrote $UNIT_DIR/$BUS_UNIT and $UNIT_DIR/$WEBUI_UNIT from copy $COPY_ID; enabled and started $BUS_NAME, then $WEBUI_UNIT"
exit 0
if [ "$PROXY" = 1 ]; then
echo "case2-unit: wrote $UNIT_DIR/$PROXY_UNIT, $UNIT_DIR/$BUS_UNIT and $UNIT_DIR/$WEBUI_UNIT from copy $COPY_ID and enabled all three"
if [ -z "$running" ]; then
echo "case2-unit: enabled and started $PROXY_UNIT, then $BUS_NAME, then $WEBUI_UNIT"
exit 0
fi
else
if [ -z "$running" ]; then
echo "case2-unit: wrote $UNIT_DIR/$BUS_UNIT and $UNIT_DIR/$WEBUI_UNIT from copy $COPY_ID; enabled and started $BUS_NAME, then $WEBUI_UNIT"
exit 0
fi
echo "case2-unit: wrote $UNIT_DIR/$BUS_UNIT and $UNIT_DIR/$WEBUI_UNIT from copy $COPY_ID and enabled both"
fi
echo "case2-unit: wrote $UNIT_DIR/$BUS_UNIT and $UNIT_DIR/$WEBUI_UNIT from copy $COPY_ID and enabled both"
for name in "$BUS_NAME" "$WEBUI_UNIT"; do
for name in "${NAMES[@]}"; do
case " $running " in
*" $name "*) echo "case2-unit: $name was already running, so it is still on ${ids[0]:+copy }${ids[0]:-the copy it started from}, not copy $COPY_ID" ;;
*) echo "case2-unit: started $name on copy $COPY_ID" ;;
esac
done
echo "case2-unit: restart $BUS_NAME and $WEBUI_UNIT together to put both on copy $COPY_ID; until then they may run different copies"
if [ "$PROXY" = 1 ]; then
echo "case2-unit: restart $PROXY_UNIT, $BUS_NAME and $WEBUI_UNIT together to put all three on copy $COPY_ID; until then they may run different copies"
else
echo "case2-unit: restart $BUS_NAME and $WEBUI_UNIT together to put both on copy $COPY_ID; until then they may run different copies"
fi