fix(case2): import library personas from the pinned copy (#1818)

(cherry picked from commit 7b4ea42171)
This commit is contained in:
jason.woltje committed 2026-10-23 18:15:16 -05:00
1 parent dca5831e7e
commit 467b05fe2b
18 files changed
+431 -5

No files matched your search

+4
View File
@@ -5429,3 +5429,7 @@ Row 299 (573, #1806, Gander, Filbert): before, the P10 band test timed a fault r
2026-10-23 | Boyd | Row 298 (#1804, Q228), targeted verification | On sage-3642 T1 lane B, five whole files serial: business scope 7/0, bus scope 14/0, scope runtime 1/0, CLI scope 4/0, project runtime 3/0 (29/0, no skips). Initial bus fixture typo recorded and corrected. M1-M10 killed by named assertions, source restored and final files green. Dewey dewey-0933 positive control and notes addressed; manifest and evidence in agents/boyd/work/queue-298/BUILD.md. Full package/script gate remains Sage's. No live scope set.
2026-10-23T22:41:07Z | Boyd | Row 298 (#1804), rebase after row 239 | Rebased onto 833ec1cc8d7c11842efb82e57c1a0b256c8d22d7 on boyd/row298-r2 without altering published boyd/row298. Only append-log conflicts; upstream bytes preserved and Boyd suffixes appended. Product merges automatic; row 239 writer-lock revoke and post-revoke error handling preserved, organization.mjs unchanged from new base. Static checks only; UNTESTED on new base pending Sage CLEAR. Details in Boyd BUILD.md.
2026-10-23 | Goldie | Row 322 / #1818 F8/F9 before | The library installer runs the clone importer, whose yaml dependency is absent on fresh clones, and tail -n 1 hides uncaught errors behind Node's version banner. Plan: run the same-commit copy importer, include it in COPY_PATHS, keep the first error line with last-line fallback, append isolated regression coverage. Mos-1550 approves setup-only world(t) additions and the one-path copy-file pin addition. No tests run yet; Sage slot required.
2026-10-23 | Goldie | Row 322 / #1818 F8/F9 after | Importer now runs from the same-commit dependency-equipped copy; COPY_PATHS includes it. One-line refusal selects an actual Error/ERR_ line, avoiding Node's source excerpt/banner, with last-line fallback. Mos-1550-approved fixture setup and one-file pin changes; two append-only regression tests. Under sage-3808 CLEAR, serialized timeout -k 10 runs in /mnt/storage/scratch/tmp/goldieuQ2664: install26/26, copy18/18, pinned-copy10/10; new tests2/2 first run and restored; N installer expected-red2/2, F8 and F9 mutants killed, installer SHA256 restored each time. No unexpected red. Evidence and exact old-test changes in agents/goldie/work/queue-322/BUILD.md; full gate and Morgana review belong to Sage.
+69
View File
@@ -0,0 +1,69 @@
# Row 322 / #1818 — F8 and F9
Goldie, 2026-10-23. Branch goldie/row322, base 833ec1cc8d7c11842efb82e57c1a0b256c8d22d7.
Brief: docs/plans/2026-10-23_host-fixes-1816-brief.md, F8/F9 section at 7333f94ba9cf.
Issue #1818 is supplied by Sage; no new issue is needed. Reviewer Morgana; Sage owns landing/full gate.
## Implementation and authority
F8 runs the importer from `$COPIES/$COMMIT/scripts/case2-persona-import.mjs`. This uses Node's normal ESM resolution, finding yaml beside the copy and the relative harness module in that same commit. It avoids a custom loader or ambient resolution setting. The copy closure already includes harness/src and business/src, but omitted the importer itself; COPY_PATHS now includes that one script. No new dependency or dependency version change.
F9 selects the first line beginning with an Error type (including TypeError etc.) or ERR_, falling back to the last line for the importer's handled refusal. awk exits after the first matching line and emits exactly one line, keeping the stack and version banner out of the install refusal. Anchoring avoids selecting Node's earlier `throw new ERR_MODULE_NOT_FOUND(...)` source excerpt instead of the actual Error line.
Mos-1550 authorizes the copy-path addition, setup-only additions to world(t), and adding the single new filename to any existing copy-file pin. Existing test edits outside the appended row-322 block:
- case2-install.test.mjs: the fake stack-copy setup gains a loop copying the importer, harness bundle.mjs/tools.mjs, and business vocabulary.mjs; it copies yaml into the fake copy's node_modules. The existing last template-string line is extended to add those setup commands. No existing assertion changes.
- packages/webui/tests/pinned-copy.test.mjs:76: the scripts file-list assertion gains only `case2-persona-import.mjs`, in sorted order. No other change in this test.
The new append-only block builds a scratch clone outside the repository with no node_modules, real installer and importer, stubbed bootstrap/business validation, and a copy with the real importer, relative module closure and yaml. F8 checks installed persona bytes/mode; F9 removes yaml from that copy to produce a real uncaught Node missing-package error, then checks one refusal line carries the cause and no stack/banner. Existing tests missed F8 because the real importer ran from the development checkout, which has yaml installed; the fake copy held only scripts/mosaic. The old fixtures thus never exercised the dependency boundary or an uncaught importer load error.
## Class check and copy-test audit
Before change:
```
grep -n 'node "$REPO/' scripts/case2-install.sh scripts/case2-precheck.sh scripts/stack-copy.sh
scripts/case2-install.sh:185: out="$(node "$REPO/scripts/case2-persona-import.mjs" ...
```
After change: same grep has no hits (exit 1). Direct clone-side .mjs entry points in the three scripts with a bare package import: **scripts/case2-persona-import.mjs → yaml**, moved to the copy by F8. No other direct entry point imports a bare package. Precheck's inline Node programs dynamically import cli/src/config.mjs, business/src/index.mjs, library/src/index.mjs and cli/src/cli.mjs; those entry files import builtins/relative modules only. Library reader.mjs uses its own front-matter parser and has no yaml import. stack-copy sources instance.sh, whose mosaic-config.mjs imports builtins only. Installer bootstrap likewise runs mosaic-config.mjs, and business validation runs business/src/cli.mjs, also no direct bare imports. No other product fix made.
Audit commands (read-only):
```
rg -n 'COPY_PATHS|copy carries|copy.*(hash|sha256|files|file set)|sha256.*copy' packages/*/tests/* scripts/test-*.sh
rg -n 'COPY_PATHS|stack-copy\.sh|readdirSync|sha256|deepEqual' packages/webui/tests/pinned-copy.test.mjs packages/cli/tests/stack-copy.test.mjs
```
Relevant hits: cli/tests/stack-copy.test.mjs:35-47 checks harness file closure (positive control: COPY_PATHS at :36), :52 asserts a required-file subset, :248-264 computes hashes/modes and compares bundle/check-out copy trees dynamically, :274 compares another bundle tree. None freezes a complete path list/hash. webui/tests/pinned-copy.test.mjs:75 pins top-level entries (unchanged), :76 pins scripts' exact file set (approved one-path update). Other readdir hits in those files check cleanup/existing-target behavior, not COPY_PATHS identity. Broad initial grep also hits business/tests/helpers.mjs:33, cli/tests/stack-upgrade.test.mjs:295, webui/tests/views.test.mjs:172, cli/tests/stack-release.test.mjs:1751 and :1987; inspected contexts are unrelated comments/UI copy or different fixture copies. No scripts/test-*.sh hit for these path/hash patterns.
## Verification plan
Pending Sage CLEAR before any test: run new tests on fixed build first, then whole case2-install.test.mjs, whole stack-copy.test.mjs and webui pinned-copy.test.mjs. Demonstrate expected failure at 094124b4e with new block applied (baseline importer path), and kill the `$REPO` reversion mutant. Record all outcomes. First unexpected new-test red is recorded/reported under mos-1272; existing-test edits outside mos-1550 or a second unexpected red stop for Sage.
No tests run yet. No commit/push yet. No host changes, live state, secrets, deploy or release actions.
## Verified results — sage-3808 CLEAR, 2026-10-23T22:47Z
All runs were serialized with `TMPDIR=/mnt/storage/scratch/tmp/goldieuQ2664` (created by `mktemp -d /mnt/storage/scratch/tmp/goldieXXXXXX`), using `timeout -k 10 90` for focused tests and `timeout -k 10 180` for whole files/negative-proof driver. Node v26.8.1. Dependencies were read through the ignored, uncommitted node_modules symlink approved by sage-3804; each scratch clone had no node_modules. No /tmp fixture was used.
| Run | Result | Evidence |
| --- | --- | --- |
| New tests, first run | 2/2 pass, exit 0 | test-new.log |
| Whole case2-install.test.mjs | 26/26 pass, exit 0 | test-install.log |
| Whole stack-copy.test.mjs | 18/18 pass, exit 0 | test-copy.log |
| Whole webui pinned-copy.test.mjs | 10/10 pass, exit 0 | test-pinned-copy.log |
| Installer from 094124b4e, new tests | 0/2 pass, exit 1, expected red | baseline-094124b4e.log |
| F8 mutant: restore `$REPO` importer path only | 0/1 pass, exit 1, killed | mutant-F8.log |
| F9 mutant: restore tail-only refusal only | 0/1 pass, exit 1, killed | mutant-F9.log |
| Fixed installer restored, new tests rerun | 2/2 pass, exit 0 | test-restored.log |
The baseline proof temporarily installs the exact installer bytes from `git show 094124b4e:scripts/case2-install.sh`, retaining the newly appended tests and approved fixture setup. Relevant installer/importer/copy/test source paths have no changes between N and the branch base 833ec1cc8 (`git diff 094124b4e 833ec1cc8 --` those paths is empty). It is a targeted baseline proof, not a full checkout/suite run at N. Baseline failures show `persona-import: Node.js v26.8.1`; the F8-only mutant exposes the underlying `Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'yaml'` from the scratch clone. F9-only mutant again prints the banner. All expected failures are deliberate acceptance checks; no unexpected first-run red occurred.
negative-proof.py records each run, checks the expected failure count/exit, restores in a finally block, and verifies fixed installer bytes after each run. Restored SHA-256 each time: `c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462`. Final focused green confirms restoration. git diff --check passed.
`git diff --numstat -- packages/cli/tests/case2-install.test.mjs`: **57 additions, 1 deletion**. Outside the new appended block, original line 70's template-string close moves after four new setup lines: the existing mkdir/empty mosaic/echo command is unchanged, followed by the `for file ...` line, mkdir/cp loop body, `done`, and mkdir/cp yaml line. All pre-existing assertions remain byte-for-byte unchanged. The only pre-existing assertion edit anywhere is the expressly approved one-filename addition at packages/webui/tests/pinned-copy.test.mjs:76.
Sage's full package/script gate and Morgana's review remain pending. This builder ran only the approved three whole test files and targeted acceptance checks. Sage retains queue transition/review posting and integration authority; no PR was created by Goldie.
Evidence log lines have trailing whitespace removed for git diff --check; test content and outcomes are unchanged.
@@ -0,0 +1,4 @@
c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462 scripts/case2-install.sh
cb1a4fef997b9696406e324878d7a0e5181892622162c9ce77a8fd625b5cd59a scripts/stack-copy.sh
a7b02af4b5df92e6d727fe8c67565ea0483d6d57531765dee68f54fd97e7c23f packages/cli/tests/case2-install.test.mjs
2574b414459f4cfb187de64b9a2fbe34ab4b4d64f582caae8baf6a1985e8cd3c packages/webui/tests/pinned-copy.test.mjs
@@ -0,0 +1,70 @@
TAP version 13
# Subtest: row 322 F8: library personas install from the copy with no clone node_modules
not ok 1 - row 322 F8: library personas install from the copy with no clone node_modules
---
duration_ms: 90.885888
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:451:1'
failureType: 'testCodeFailure'
error: |-
case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-rQM4ct/home/.config/mosaic-dev/config.json
case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-rQM4ct/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc
case2-install: refused: persona-import: Node.js v26.8.1
1 !== 0
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 0
actual: 1
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:454: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)
...
# Subtest: row 322 F9: importer refusal keeps the error line rather than the Node banner
not ok 2 - row 322 F9: importer refusal keeps the error line rather than the Node banner
---
duration_ms: 91.239243
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:462:1'
failureType: 'testCodeFailure'
error: |-
The input did not match the regular expression /persona-import: Error \[ERR_MODULE_NOT_FOUND\]: Cannot find package 'yaml'/. Input:
'case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-O6VWs2/home/.config/mosaic-dev/config.json\n' +
'case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-O6VWs2/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc\n' +
'case2-install: refused: persona-import: Node.js v26.8.1\n'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
actual: |-
case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-O6VWs2/home/.config/mosaic-dev/config.json
case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-O6VWs2/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc
case2-install: refused: persona-import: Node.js v26.8.1
operator: 'match'
stack: |-
refused (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:137:10)
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:469:3)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.processPendingSubtests (node:internal/test_runner/test:974:18)
Test.postRun (node:internal/test_runner/test:1542:19)
Test.run (node:internal/test_runner/test:1467:12)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
1..2
# tests 2
# suites 0
# pass 0
# fail 2
# cancelled 0
# skipped 0
# todo 0
# duration_ms 287.462723
@@ -0,0 +1,37 @@
TAP version 13
# Subtest: row 322 F8: library personas install from the copy with no clone node_modules
not ok 1 - row 322 F8: library personas install from the copy with no clone node_modules
---
duration_ms: 90.468116
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:451:1'
failureType: 'testCodeFailure'
error: |-
case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-bzVkal/home/.config/mosaic-dev/config.json
case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-bzVkal/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc
case2-install: refused: persona-import: Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'yaml' imported from /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-bzVkal/clone/scripts/case2-persona-import.mjs
1 !== 0
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 0
actual: 1
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:454: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 194.231229
@@ -0,0 +1,41 @@
TAP version 13
# Subtest: row 322 F9: importer refusal keeps the error line rather than the Node banner
not ok 1 - row 322 F9: importer refusal keeps the error line rather than the Node banner
---
duration_ms: 92.814376
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:462:1'
failureType: 'testCodeFailure'
error: |-
The input did not match the regular expression /persona-import: Error \[ERR_MODULE_NOT_FOUND\]: Cannot find package 'yaml'/. Input:
'case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-6HcR0m/home/.config/mosaic-dev/config.json\n' +
'case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-6HcR0m/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc\n' +
'case2-install: refused: persona-import: Node.js v26.8.1\n'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
actual: |-
case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-6HcR0m/home/.config/mosaic-dev/config.json
case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-6HcR0m/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc
case2-install: refused: persona-import: Node.js v26.8.1
operator: 'match'
stack: |-
refused (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:137:10)
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:469:3)
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 200.516406
@@ -0,0 +1,6 @@
baseline-094124b4e: exit 1, expected failures 2
restored sha256 c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462
mutant-F8: exit 1, expected failures 1
restored sha256 c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462
mutant-F9: exit 1, expected failures 1
restored sha256 c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462
@@ -0,0 +1,39 @@
"""Run row-322 expected-red proofs, always restoring installer bytes."""
import hashlib
import os
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[4]
installer = root / 'scripts/case2-install.sh'
logs = Path(__file__).resolve().parent
fixed = installer.read_bytes()
baseline = subprocess.check_output(['git', 'show', '094124b4e:scripts/case2-install.sh'], cwd=root)
f8 = fixed.replace(b'$COPIES/$COMMIT/scripts/case2-persona-import.mjs', b'$REPO/scripts/case2-persona-import.mjs')
assert f8 != fixed
old_error = baseline.splitlines()[185]
new_error = fixed.splitlines()[185]
f9 = fixed.replace(new_error, old_error)
assert f9 != fixed
try:
for label, source, pattern, failures in [
('baseline-094124b4e', baseline, 'row 322', 2),
('mutant-F8', f8, 'row 322 F8', 1),
('mutant-F9', f9, 'row 322 F9', 1),
]:
installer.write_bytes(source)
result = subprocess.run([
'timeout', '-k', '10', '90', 'node', '--test', '--test-reporter=tap',
f'--test-name-pattern={pattern}', 'packages/cli/tests/case2-install.test.mjs',
], cwd=root, env=os.environ, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(logs / f'{label}.log').write_text(result.stdout)
print(f'{label}: exit {result.returncode}, expected failures {failures}', flush=True)
assert result.returncode == 1, result.stdout
assert f'# fail {failures}\n' in result.stdout, result.stdout
assert 'ERR_MODULE_NOT_FOUND' in result.stdout, result.stdout
installer.write_bytes(fixed)
assert installer.read_bytes() == fixed
print('restored sha256 ' + hashlib.sha256(fixed).hexdigest(), flush=True)
finally:
installer.write_bytes(fixed)
assert installer.read_bytes() == fixed
@@ -0,0 +1,26 @@
✔ a copy carries every file the harness modules read from it (891.310838ms)
✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (537.82282ms)
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (713.621566ms)
✔ row 190 N6: check --instance passes only when the wrapper exports the instance's MOSAIC_CONFIG (657.082198ms)
✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (799.380065ms)
✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (499.872009ms)
✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (3824.368055ms)
✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (668.500662ms)
✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (399.490955ms)
✔ install never shows a template name on systemd 261; template drop-ins still refuse (318.110324ms)
✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (347.788225ms)
✔ inherited mosaic-.service.d override refuses without changing the unit (403.142763ms)
✔ inherited service.d override refuses without changing the unit (384.337811ms)
✔ inherited drop-in unreadable directory or file refuses before writing (370.630432ms)
✔ row 232: copy --bundle copies a SHA the bundle's refactor reaches, byte-identical to copy SHA, and refuses the rest (2257.394077ms)
✔ row 232: wrapper --create writes the first wrapper only, the same bytes wrapper ID writes, and refuses over any file (779.276348ms)
✔ row 232: check --units external reads the system units, the default reads the user units (641.792634ms)
✔ Vikunja 614: bundle and checkout copy leave an unreadable caller directory before find (1047.143772ms)
ℹ tests 18
ℹ suites 0
ℹ pass 18
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 15638.417631
@@ -0,0 +1,34 @@
✔ the library install writes the business file, personas and slot dir, makes the copy and starts nothing (258.707115ms)
✔ a library re-run passes and leaves every file as it was (391.278216ms)
✔ a re-run refuses business-file-differs and leaves the file unchanged (274.686097ms)
✔ a re-run refuses persona-differs when an installed persona isn't the staged one, and writes nothing (274.084555ms)
✔ the factory install writes no library mapping, persona or slot dir (175.791904ms)
✔ the factory install refuses a library project and bus.tester off, before anything is written (72.301025ms)
✔ the library install refuses bus.tester on and a persona var in the input (76.469642ms)
✔ the install refuses root, a business id mismatch, another --home and a config dir with group bits (57.89739ms)
✔ the library install needs --personas (5.495139ms)
✔ a validate refusal removes the business file this run wrote (230.311754ms)
✔ N4: a validate refusal on a re-run leaves the matching business file in place, byte for byte (397.123924ms)
✔ a clean library host passes the precheck with no mirror yet (404.408689ms)
✔ the library precheck refuses a persona file at 0644 (399.17119ms)
✔ the library precheck refuses a persona whose digest isn't persona.sha256 (402.47159ms)
✔ the library precheck refuses a persona dir with group bits (390.108413ms)
✔ the library precheck refuses a business file at 0644 (402.829663ms)
✔ the library precheck refuses a changes.jsonl at 0644 (573.861228ms)
✔ the library precheck refuses a Discord binding (386.405794ms)
✔ --library-status refuses library-mirror-missing before the bus has made the mirror (309.213305ms)
✔ --library-status refuses library-never-synced on a fresh mirror (330.922116ms)
✔ --library-status names a recorded lastError before the null lastFetch it leaves (N3) (335.576032ms)
✔ --library-status prints each R-problem's path as an escaped JSON string, then the count, and passes (379.69927ms)
✔ --library-status takes no --profile (5.544173ms)
✔ Vikunja 614: install preserves the explicit notifier decision bytes and mode and creates none when absent (465.015156ms)
✔ row 322 F8: library personas install from the copy with no clone node_modules (139.662489ms)
✔ row 322 F9: importer refusal keeps the error line rather than the Node banner (95.452368ms)
ℹ tests 26
ℹ suites 0
ℹ pass 26
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 7340.739711
+10
View File
@@ -0,0 +1,10 @@
✔ row 322 F8: library personas install from the copy with no clone node_modules (178.794273ms)
✔ row 322 F9: importer refusal keeps the error line rather than the Node banner (101.739988ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 394.59041
@@ -0,0 +1,18 @@
✔ copy writes the Console paths at the full id, read-only, and leaves an existing copy alone (872.104795ms)
✔ copy refuses a commit off refactor, an unknown one, a worktree and a bad copies directory (1245.376299ms)
✔ copy writes nothing when an archived path is missing at the commit (409.033887ms)
✔ a copy that loses the rename race leaves no read-only temp directory behind (548.743561ms)
✔ a copy whose rename fails with no target refuses, writes no copy and leaves no temp directory (534.468073ms)
✔ a copy that meets a target holding files says exists: and moves nothing into it (541.303916ms)
✔ a copy that meets an empty target directory replaces it by rename, not by moving into it (547.966283ms)
✔ serve runs from a copy: /healthz 200, the config check, roles and RELEASE from the copy, the queue from --checkout (856.865742ms)
✔ serve refuses a --checkout that is not an absolute directory path (274.552284ms)
✔ consoleReads runs the queue read under queueRoot and reads RELEASE under root (49.311324ms)
ℹ tests 10
ℹ suites 0
ℹ pass 10
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 6003.825092
@@ -0,0 +1,10 @@
✔ row 322 F8: library personas install from the copy with no clone node_modules (148.410784ms)
✔ row 322 F9: importer refusal keeps the error line rather than the Node banner (94.205865ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 344.990842
+2
View File
@@ -1603,3 +1603,5 @@ are never rewritten or removed; corrections are new entries.
2026-10-23T19:29:00Z | Boyd (48a3ae38) | Row 298 (#1804) targeted T1 lane B | Five whole files 29/0/0, ten mutants killed, source restored; reviewer delta prepared. Earlier startup timestamp corrected in Boyd BUILD.md. Full gate and queue/landing remain Sage's.
2026-10-23T22:41:07Z | Boyd (48a3ae38) | Row 298 rebase under sage-3794; row 321 review assigned | Rebased candidate on boyd/row298-r2 at base833ec1cc8; append-only log conflicts resolved preserving upstream, writer-lock revoke retained; no Node/tests before CLEAR. Row321 awaits pushed candidate/review request.
2026-10-23 | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 322 / #1818 F8/F9 | Investigated clone dependency failure, recovered pinned brief and queue assignment; building on goldie/row322 under mos-1550, tests pending Sage CLEAR.
2026-10-23T22:48Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 322 / #1818 F8/F9 builder verification | sage-3808 slot: 54/54 across three whole files, new tests first-run/restored2/2, original-N installer and both mutants fail as intended; bytes restored. Candidate ready for goldie/row322 push and Morgana review; no merge/full-gate claim.
+57 -1
View File
@@ -67,7 +67,11 @@ function world(t, { profile = "library" } = {}) {
const log = join(root, "calls.log");
script(join(bin, "stack-copy"), `echo "stack-copy $*" >>'${log}'
[ "$1" = copy ] && [ "$3" = --copies ] || exit 4
mkdir -p "$4/$2/scripts" && : >"$4/$2/scripts/mosaic" && echo "copied: $4/$2"`);
mkdir -p "$4/$2/scripts" && : >"$4/$2/scripts/mosaic" && echo "copied: $4/$2"
for file in scripts/case2-persona-import.mjs packages/harness/src/bundle.mjs packages/harness/src/tools.mjs packages/business/src/vocabulary.mjs; do
mkdir -p "$4/$2/$(dirname "$file")" && cp '${REPO}'"$file" "$4/$2/$file" || exit 1
done
mkdir -p "$4/$2/node_modules" && cp -R '${REPO}node_modules/yaml' "$4/$2/node_modules/yaml"`);
script(join(bin, "systemctl"), `echo "systemctl $*" >>'${log}'`);
const me = spawnSync("id", ["-un"], { encoding: "utf8" }).stdout.trim();
script(join(bin, "getent"), `[ "$1" = passwd ] && [ "$2" = '${me}' ] && echo '${me}:x:${UID}:${GID}::${home}:/bin/bash'`);
@@ -417,3 +421,55 @@ test("Vikunja 614: install preserves the explicit notifier decision bytes and mo
assert.equal(fresh.code, 0, fresh.out);
assert.equal(existsSync(missing), false);
});
// Row 322 (#1818), F8/F9: a clone without dependencies and a dependency-equipped copy.
import { cpSync } from "node:fs";
function dependencyFreeClone(t) {
const x = world(t);
const clone = join(x.root, "clone");
mkdirSync(join(clone, "scripts"), { recursive: true });
for (const file of ["case2-install.sh", "case2-persona-import.mjs"]) {
cpSync(join(REPO, "scripts", file), join(clone, "scripts", file));
}
// Isolate the persona step; bootstrap and business validation are covered above.
script(join(clone, "scripts", "bootstrap.sh"), "exit 0");
script(join(clone, "scripts", "mosaic"), "exit 0");
assert.equal(existsSync(join(clone, "node_modules")), false);
const run = () => {
const r = spawnSync(join(clone, "scripts", "case2-install.sh"), [
"--business", "acme", "--business-file", x.input, "--commit", COMMIT,
"--profile", "library", "--personas", x.staging,
], { env: env(x), encoding: "utf8", timeout: 30000 });
const out = r.stdout + r.stderr;
clean(out);
return { code: r.status, out };
};
return { ...x, clone, run };
}
test("row 322 F8: library personas install from the copy with no clone node_modules", (t) => {
const x = dependencyFreeClone(t);
const r = x.run();
assert.equal(r.code, 0, r.out);
for (const instance of ["bean", "chet"]) {
assert.equal(readFileSync(join(x.personaDir, `${instance}.md`), "utf8"), PERSONA_TEXT[instance]);
assert.equal(mode(join(x.personaDir, `${instance}.md`)), 0o600);
}
assert.equal(existsSync(join(x.clone, "node_modules")), false);
});
test("row 322 F9: importer refusal keeps the error line rather than the Node banner", (t) => {
const x = dependencyFreeClone(t);
const copy = join(x.home, ".local", "share", "mosaic-stack", "copies", COMMIT);
// Real Node module-load error: the error precedes its stack and version banner.
const fake = join(x.bin, "stack-copy");
writeFileSync(fake, `${readFileSync(fake, "utf8")}\nrm -rf '${copy}/node_modules/yaml'\n`);
const r = x.run();
refused(r, /persona-import: Error \[ERR_MODULE_NOT_FOUND\]: Cannot find package 'yaml'/);
const refusal = r.out.split("\n").filter((line) => line.includes("refused:"));
assert.equal(refusal.length, 1, r.out);
assert.doesNotMatch(r.out, /Node\.js v|\n\s+at /);
assert.equal(existsSync(x.business), false);
assert.equal(existsSync(x.personaDir), false);
});
+1 -1
View File
@@ -73,7 +73,7 @@ test('copy writes the Console paths at the full id, read-only, and leaves an exi
assert.match(r.stdout, new RegExp(`^copied: ${target}$`, 'm'));
assert.deepEqual(readdirSync(copies), [head], 'no temp directory left behind');
assert.deepEqual(readdirSync(target).sort(), ['RELEASE', 'adapters', 'package-lock.json', 'package.json', 'packages', 'roles', 'scripts', 'tools']);
assert.deepEqual(readdirSync(join(target, 'scripts')), ['mosaic', 'mosaic-config.mjs']);
assert.deepEqual(readdirSync(join(target, 'scripts')), ['case2-persona-import.mjs', 'mosaic', 'mosaic-config.mjs']);
assert.ok(existsSync(join(target, 'packages', 'webui', 'README.md')), "the unit's Documentation=");
assert.equal(statSync(join(target, 'packages', 'webui', 'README.md')).mode & 0o222, 0, 'README.md is read-only');
assert.ok(existsSync(join(target, 'packages', 'tasks', 'fields.json')), 'the task field table');
+2 -2
View File
@@ -182,8 +182,8 @@ say "copy $COPIES/$COMMIT"
# 4. library: the personas, from the staging dir.
if [ "$PROFILE" = library ]; then
if [ "$PERSONA_STATE" = install ]; then
out="$(node "$REPO/scripts/case2-persona-import.mjs" install --staging "$PERSONAS" --config-dir "$CONFIG_DIR" --business "$BUSINESS" 2>&1)" \
|| refuse "persona-import: $(printf '%s' "$out" | tail -n 1)"
out="$(node "$COPIES/$COMMIT/scripts/case2-persona-import.mjs" install --staging "$PERSONAS" --config-dir "$CONFIG_DIR" --business "$BUSINESS" 2>&1)" \
|| refuse "persona-import: $(printf '%s\n' "$out" | awk '/^[[:alnum:]_]*Error|^ERR_/ { print; found=1; exit } { last=$0 } END { if (!found) print last }')"
while IFS=$'\t' read -r _ inst name sum; do
printf '%s\n' "$out" | grep -qxF "instance $inst: persona.name $name, persona.sha256 $sum" \
|| refuse "persona-import: the import did not report $inst with the manifest's sha256"
+1 -1
View File
@@ -27,7 +27,7 @@ copy_root() { [[ "$ID" =~ ^[0-9a-f]{40}$ ]] || die '--copy must be a full 40-cha
# Closure of scripts/mosaic, bus process/notifier/launcher, Console serve,
# human-cli and both harness adapters, including on-demand children/data.
COPY_PATHS=(
scripts/mosaic scripts/mosaic-config.mjs
scripts/mosaic scripts/mosaic-config.mjs scripts/case2-persona-import.mjs
adapters roles RELEASE package.json package-lock.json
packages/bus/src packages/bus/schema.sql packages/business/src packages/cli/src packages/project
packages/conversation/src packages/discord/src packages/discord/bin packages/discord/extension packages/harness/src