review(56): Darkwing round 2 packet, approve (#1545 comment 27123)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-10 12:17:11 -05:00
co-authored by Claude Opus 5.5
parent cab5f10e11
commit 8e46a8619d
68 changed files with 3172 additions and 0 deletions
@@ -0,0 +1,15 @@
1afe07062349b097b955f0391bffb839b083492d02644421def6c39cc19ff4a4 agents/rocko/work/queue-56/byte-check.sh
09788e902a54207ef5f94de1c8b7f2f31c049fb028fa68847115da83060d90f9 agents/rocko/work/queue-56/delta-check.sh
15d575cc45313906114f7edda9379b66af7624d9e894bb98812deb7acf5d87ba agents/rocko/work/queue-56/seed.sh
146b236a37bf665979e28f1794c4c4437b6e85feb43b892e8d9d697506fc453f packages/runs/package.json
895dd393a43221ffb12a5dc1b76ab61118b5562a5d7acbc50450ebb3ea6388d3 packages/runs/README.md
f686212fa10e8541a1d8055fe30d65558a516245276aa53ecefd97bd7ca0a20c packages/runs/src/errors.mjs
eb7063a8ee0417699b45f4bfec69c3ffcde8b3b276956ca4fea266ec0abd6b23 packages/runs/src/index.mjs
3f9d025f0615a08edd440bb2aa6cea48f21cafa1f680332fffca3c7ee586cc65 packages/runs/src/paths.mjs
ba64e50c3a63e693fc313278ff6cf440234649a6719dd9229bce20272a518ec1 packages/runs/src/runs.mjs
ae546df81c6da7ef51680e23a12c01f08c5a6dc1b68cdf26c8d4210772149940 packages/runs/src/state.mjs
7c941596020b14d8627ccb09b66e0e51195318e86add60eba9d2c4409e6f8dd6 packages/runs/tests/helpers.mjs
fbaa092b5c8a1b54bc52c090da71f0da4313b4a8221fd7a2e89404a2a1b04633 packages/runs/tests/runs.test.mjs
d0f33cb945cb5960847cd9e9af05f5a85731cb68bc5131efb084a7ecea81591f packages/runs/tests/state.test.mjs
be5d1bdf95340e6c4a697531a225f08b40901ad1b570da7371efe7994e3e1413 packages/runs/tests/task-cli.test.mjs
4873187609897c643e62acf7f1d074fa3c03369fe6702b9b0c7776138e776401 scripts/mosaic-task.mjs
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
# Row 56 gate: packages/runs node suite, then every scripts/test-*.sh with Docker unreachable.
export TMPDIR=~/darkwing-scratch/r56b/tmp DOCKER_HOST=unix:///nonexistent.sock
cd ~/darkwing-scratch/r56b/cand
O=~/darkwing-scratch/r56b/out
: > $O/summary.txt
echo "start $(date -u +%FT%TZ)" >> $O/summary.txt
env -u NODE_TEST_CONTEXT node --test packages/runs/tests/*.test.mjs > $O/node-runs.txt 2>&1; e=$?
echo "node-runs exit=$e $(grep -E '^ℹ (pass|fail)' $O/node-runs.txt | tr '\n' ' ')" >> $O/summary.txt
for f in scripts/test-*.sh; do
s=$(basename $f .sh)
$f > $O/$s.txt 2>&1; e=$?
echo "$s exit=$e $(grep -E 'passed, [0-9]+ failed' $O/$s.txt | tail -1)" >> $O/summary.txt
done
echo "end $(date -u +%FT%TZ)" >> $O/summary.txt
echo DONE >> $O/summary.txt
@@ -0,0 +1,13 @@
diff --git a/scripts/mosaic-task.mjs b/scripts/mosaic-task.mjs
index cf8399a0..b5a9564e 100755
--- a/scripts/mosaic-task.mjs
+++ b/scripts/mosaic-task.mjs
@@ -486,7 +486,7 @@ function listRuns() {
}
function showRun(runId) {
- if (!isRunId(runId)) {
+ if (false) {
fail(4, `invalid run id: ${JSON.stringify(runId)} (expected r-<id>)`);
}
const resolved = loadConfig();
@@ -0,0 +1,73 @@
✔ isRunId accepts the run id shape and nothing else (1.187366ms)
✔ a missing data root or runs directory lists nothing (2.073148ms)
✔ runs as a regular file lists nothing, as before (0.667967ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.065405ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.710197ms)
✔ readRunRecord returns documents and artifacts in directory order (1.212073ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.794819ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.87404ms)
✔ readRunDocument reads only the three run documents (0.73155ms)
✔ a link inside the data root is followed (1.101065ms)
✔ a data root that is itself a link is trusted as configured (0.92173ms)
✔ a run directory linked out of the data root is never read (1.182722ms)
✔ a document linked out of the data root reads as null (1.519156ms)
✔ a runs directory linked out of the data root refuses (0.971294ms)
✔ a relative link that climbs out of the data root refuses (0.602539ms)
✔ a sibling whose name starts with the data root's name is outside it (0.602411ms)
✔ a link loop refuses instead of reading as missing (0.836417ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.588817ms)
✔ an unreadable run directory refuses instead of reading as missing (0.764263ms)
✔ a link to the data root's parent is outside it (0.478523ms)
✔ listRunIds sorts whatever order the directory returns (1.303967ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (1.087459ms)
✔ the readers write nothing (3.286472ms)
✔ no pointer is null (2.356044ms)
✔ the pointer release.sh writes reads back (1.496897ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (2.651413ms)
✔ a pointer that is a directory refuses with exit 4 (1.669957ms)
✔ a state file linked out of the data root refuses (2.02127ms)
✔ a state directory linked out of the data root refuses (1.056491ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.702711ms)
✔ a missing log is empty (1.296972ms)
✔ the log reads oldest first and counts malformed lines (1.656559ms)
✔ last must be a positive integer (0.721034ms)
✔ the state readers write nothing (1.365288ms)
✔ list prints each run in the established format (100.715833ms)
✔ show prints the run in the established format (108.835407ms)
✔ show of a missing run and an invalid id exit 4 as before (180.126942ms)
✔ list and show refuse a runs directory linked out of the data root (192.410504ms)
✔ show refuses a run linked out of the data root; list reports it unknown (163.544111ms)
✔ list shows an r- name that isn't a valid run id, as before (74.327067ms)
✖ show refuses an invalid id before reading the config (172.108882ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1064.413393
✖ failing tests:
test at packages/runs/tests/task-cli.test.mjs:104:1
✖ show refuses an invalid id before reading the config (172.108882ms)
AssertionError [ERR_ASSERTION]: mosaic-task: configuration problem (exit 3); run scripts/bootstrap.sh or fix config.json
3 !== 4
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/task-cli.test.mjs:109:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 3,
expected: 4,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,14 @@
diff --git a/packages/runs/src/paths.mjs b/packages/runs/src/paths.mjs
index 1dbb5539..f7c5f4ea 100644
--- a/packages/runs/src/paths.mjs
+++ b/packages/runs/src/paths.mjs
@@ -43,7 +43,8 @@ export function readJsonObject(dataRoot, ...parts) {
if (file === null) return null;
const value = JSON.parse(fs.readFileSync(file, "utf8"));
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
- } catch {
+ } catch (error) {
+ if (error instanceof RunsError) throw error;
return null;
}
}
@@ -0,0 +1,121 @@
✔ isRunId accepts the run id shape and nothing else (1.400398ms)
✔ a missing data root or runs directory lists nothing (1.988221ms)
✔ runs as a regular file lists nothing, as before (0.707746ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.622032ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (1.758806ms)
✔ readRunRecord returns documents and artifacts in directory order (1.221436ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.088578ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.038079ms)
✔ readRunDocument reads only the three run documents (0.884663ms)
✔ a link inside the data root is followed (2.038475ms)
✔ a data root that is itself a link is trusted as configured (1.134007ms)
✖ a run directory linked out of the data root is never read (0.752745ms)
✖ a document linked out of the data root reads as null (0.763986ms)
✔ a runs directory linked out of the data root refuses (0.653651ms)
✔ a relative link that climbs out of the data root refuses (0.478299ms)
✔ a sibling whose name starts with the data root's name is outside it (0.531338ms)
✖ a link loop refuses instead of reading as missing (0.601651ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.458441ms)
✔ an unreadable run directory refuses instead of reading as missing (0.547674ms)
✔ a link to the data root's parent is outside it (0.51465ms)
✔ listRunIds sorts whatever order the directory returns (0.836887ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.736515ms)
✔ the readers write nothing (2.225785ms)
✔ no pointer is null (2.030231ms)
✔ the pointer release.sh writes reads back (1.320527ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (135.180496ms)
✔ a pointer that is a directory refuses with exit 4 (0.722154ms)
✔ a state file linked out of the data root refuses (1.394205ms)
✔ a state directory linked out of the data root refuses (0.524668ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.412215ms)
✔ a missing log is empty (0.308456ms)
✔ the log reads oldest first and counts malformed lines (0.862537ms)
✔ last must be a positive integer (0.41004ms)
✔ the state readers write nothing (0.697997ms)
✔ list prints each run in the established format (102.363954ms)
✔ show prints the run in the established format (99.691103ms)
✔ show of a missing run and an invalid id exit 4 as before (133.511964ms)
✔ list and show refuse a runs directory linked out of the data root (188.487781ms)
✖ show refuses a run linked out of the data root; list reports it unknown (167.45552ms)
✔ list shows an r- name that isn't a valid run id, as before (88.153586ms)
✔ show refuses an invalid id before reading the config (62.195932ms)
ℹ tests 41
ℹ suites 0
ℹ pass 37
ℹ fail 4
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 905.338616
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:119:1
✖ a run directory linked out of the data root is never read (0.752745ms)
Error [RunsError]: /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-LFk8Oo/data/runs/r-20260101T000000Z-aaaaaa/result.json resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-LFk8Oo/data)
at resolveInside (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/paths.mjs:33:11)
at readJsonObject (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/paths.mjs:42:18)
at file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:46:13
at Array.map (<anonymous>)
at listRunRecords (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:44:31)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:125:20)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19) {
exitCode: 4
}
test at packages/runs/tests/runs.test.mjs:129:1
✖ a document linked out of the data root reads as null (0.763986ms)
Error [RunsError]: /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-ge63ie/data/runs/r-20260101T000000Z-aaaaaa/result.json resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-ge63ie/data)
at resolveInside (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/paths.mjs:33:11)
at readJsonObject (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/paths.mjs:42:18)
at readRunDocument (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:37:10)
at readRunRecord (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:67:13)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:136:18)
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) {
exitCode: 4
}
test at packages/runs/tests/runs.test.mjs:168:1
✖ a link loop refuses instead of reading as missing (0.601651ms)
Error [RunsError]: cannot resolve /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-HLtM1w/data/runs/r-20260101T000000Z-aaaaaa/result.json: ELOOP
at resolveInside (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/paths.mjs:30:11)
at readJsonObject (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/paths.mjs:42:18)
at file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:46:13
at Array.map (<anonymous>)
at listRunRecords (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:44:31)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:174:20)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19) {
exitCode: 4
}
test at packages/runs/tests/task-cli.test.mjs:82:1
✖ show refuses a run linked out of the data root; list reports it unknown (167.45552ms)
AssertionError [ERR_ASSERTION]: mosaic-task: /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-QZ52Qw/data/runs/r-20260101T000000Z-aaaaaa/result.json resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-QZ52Qw/data)
4 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/task-cli.test.mjs:92:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/runs/src/runs.mjs b/packages/runs/src/runs.mjs
index 03730c33..5096f921 100644
--- a/packages/runs/src/runs.mjs
+++ b/packages/runs/src/runs.mjs
@@ -52,7 +52,6 @@ export function listRunRecords(dataRoot) {
export function readRunRecord(dataRoot, runId) {
requireRunId(runId);
// The runs directory first, so a refusal names the link that escapes.
- if (resolveInside(dataRoot, RUNS_DIRNAME) === null) return null;
const dir = resolveInside(dataRoot, RUNS_DIRNAME, runId);
if (dir === null) return null;
let artifacts;
@@ -0,0 +1,103 @@
✔ isRunId accepts the run id shape and nothing else (1.065207ms)
✔ a missing data root or runs directory lists nothing (1.698841ms)
✔ runs as a regular file lists nothing, as before (0.630984ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.49914ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.076647ms)
✔ readRunRecord returns documents and artifacts in directory order (1.038104ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.613748ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.680147ms)
✔ readRunDocument reads only the three run documents (0.554692ms)
✔ a link inside the data root is followed (0.956197ms)
✔ a data root that is itself a link is trusted as configured (0.633126ms)
✔ a run directory linked out of the data root is never read (0.687083ms)
✔ a document linked out of the data root reads as null (0.817475ms)
✖ a runs directory linked out of the data root refuses (1.147289ms)
✔ a relative link that climbs out of the data root refuses (0.420625ms)
✔ a sibling whose name starts with the data root's name is outside it (0.432331ms)
✔ a link loop refuses instead of reading as missing (0.580696ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.420343ms)
✔ an unreadable run directory refuses instead of reading as missing (0.505363ms)
✔ a link to the data root's parent is outside it (0.365301ms)
✔ listRunIds sorts whatever order the directory returns (0.728115ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.572555ms)
✔ the readers write nothing (2.015551ms)
✔ no pointer is null (2.257117ms)
✔ the pointer release.sh writes reads back (1.314201ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (132.331487ms)
✔ a pointer that is a directory refuses with exit 4 (1.218663ms)
✔ a state file linked out of the data root refuses (0.870713ms)
✔ a state directory linked out of the data root refuses (0.504281ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.501277ms)
✔ a missing log is empty (0.321384ms)
✔ the log reads oldest first and counts malformed lines (0.866945ms)
✔ last must be a positive integer (0.46119ms)
✔ the state readers write nothing (0.714278ms)
✔ list prints each run in the established format (77.057222ms)
✔ show prints the run in the established format (65.031466ms)
✔ show of a missing run and an invalid id exit 4 as before (124.475617ms)
✖ list and show refuse a runs directory linked out of the data root (170.366112ms)
✔ show refuses a run linked out of the data root; list reports it unknown (163.564218ms)
✔ list shows an r- name that isn't a valid run id, as before (76.840622ms)
✔ show refuses an invalid id before reading the config (49.249577ms)
ℹ tests 41
ℹ suites 0
ℹ pass 39
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 801.494548
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:143:1
✖ a runs directory linked out of the data root refuses (1.147289ms)
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /runs resolves outside the data root/. Input:
'RunsError: /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-ZzTrSZ/data/runs/r-20260101T000000Z-aaaaaa resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-ZzTrSZ/data)'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:149:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: RunsError: /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-ZzTrSZ/data/runs/r-20260101T000000Z-aaaaaa resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-ZzTrSZ/data)
at resolveInside (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/paths.mjs:33:11)
at readRunRecord (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:55:15)
at file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:149:23
at getActual (node:assert:580:5)
at strict.throws (node:assert:728:24)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:149: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),
expected: /runs resolves outside the data root/,
operator: 'throws',
diff: 'simple'
}
test at packages/runs/tests/task-cli.test.mjs:70:1
✖ list and show refuse a runs directory linked out of the data root (170.366112ms)
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /^mosaic-task: .*runs resolves outside the data root/. Input:
'mosaic-task: /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-p4YqkE/data/runs/r-20260101T000000Z-aaaaaa resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-p4YqkE/data)\n'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/task-cli.test.mjs:78:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'mosaic-task: /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-p4YqkE/data/runs/r-20260101T000000Z-aaaaaa resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-p4YqkE/data)\n',
expected: /^mosaic-task: .*runs resolves outside the data root/,
operator: 'match',
diff: 'simple'
}
@@ -0,0 +1,11 @@
diff --git a/packages/runs/src/state.mjs b/packages/runs/src/state.mjs
index af5cc927..3c78f1a3 100644
--- a/packages/runs/src/state.mjs
+++ b/packages/runs/src/state.mjs
@@ -79,5 +79,5 @@ export function readActivationLog(dataRoot, { last } = {}) {
if (entry === null) malformed += 1;
else entries.push(entry);
}
- return { entries: last === undefined ? entries : entries.slice(-last), malformed };
+ return { entries: last === undefined ? entries : entries.slice(0, last), malformed };
}
@@ -0,0 +1,98 @@
✔ isRunId accepts the run id shape and nothing else (1.07446ms)
✔ a missing data root or runs directory lists nothing (9.100856ms)
✔ runs as a regular file lists nothing, as before (5.813261ms)
✔ listRunIds keeps r- names only, sorted oldest first (17.34251ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (1.86616ms)
✔ readRunRecord returns documents and artifacts in directory order (26.002078ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (3.693151ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.008653ms)
✔ readRunDocument reads only the three run documents (0.813628ms)
✔ a link inside the data root is followed (1.508103ms)
✔ a data root that is itself a link is trusted as configured (1.817532ms)
✔ a run directory linked out of the data root is never read (1.130005ms)
✔ a document linked out of the data root reads as null (22.199116ms)
✔ a runs directory linked out of the data root refuses (1.18345ms)
✔ a relative link that climbs out of the data root refuses (0.698018ms)
✔ a sibling whose name starts with the data root's name is outside it (0.773861ms)
✔ a link loop refuses instead of reading as missing (1.009039ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.659472ms)
✔ an unreadable run directory refuses instead of reading as missing (0.692031ms)
✔ a link to the data root's parent is outside it (0.58212ms)
✔ listRunIds sorts whatever order the directory returns (1.167948ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.76835ms)
✔ the readers write nothing (3.410202ms)
✔ no pointer is null (5.069465ms)
✔ the pointer release.sh writes reads back (6.698682ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (48.474502ms)
✔ a pointer that is a directory refuses with exit 4 (1.375452ms)
✔ a state file linked out of the data root refuses (0.982791ms)
✔ a state directory linked out of the data root refuses (0.595852ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.475898ms)
✔ a missing log is empty (1.243422ms)
✖ the log reads oldest first and counts malformed lines (2.965998ms)
✔ last must be a positive integer (0.75225ms)
✔ the state readers write nothing (1.350016ms)
✔ list prints each run in the established format (78.922855ms)
✔ show prints the run in the established format (95.821873ms)
✔ show of a missing run and an invalid id exit 4 as before (127.675112ms)
✔ list and show refuse a runs directory linked out of the data root (177.883313ms)
✔ show refuses a run linked out of the data root; list reports it unknown (197.934639ms)
✔ list shows an r- name that isn't a valid run id, as before (99.346507ms)
✔ show refuses an invalid id before reading the config (67.610285ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 908.204283
✖ failing tests:
test at packages/runs/tests/state.test.mjs:87:1
✖ the log reads oldest first and counts malformed lines (2.965998ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
{
entries: [
{
+ at: '2026-09-03T20:57:00Z',
+ event: 'package',
+ imageTag: 'mosaic-poc-agent:0.84.4-r0.0.12',
+ release: '0.0.12'
- at: '2026-09-03T20:57:50Z',
- event: 'refused',
- imageTag: 'mosaic-poc-agent:0.84.4-r0.0.11',
- note: 'health check failed (fault-injected)',
- release: '0.0.11'
},
{
+ at: '2026-09-03T20:57:47Z',
+ event: 'activate',
+ imageTag: 'mosaic-poc-agent:0.84.4-r0.0.12',
+ release: '0.0.12'
- at: '2026-09-03T20:58:15Z',
- event: 'rollback',
- imageTag: 'mosaic-poc-agent:0.84.4-r0.0.11',
- release: '0.0.11'
}
],
malformed: 7
}
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/state.test.mjs:111:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: { entries: [ [Object], [Object] ], malformed: 7 },
expected: { entries: [ [Object], [Object] ], malformed: 7 },
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/runs/src/state.mjs b/packages/runs/src/state.mjs
index af5cc927..73c4093f 100644
--- a/packages/runs/src/state.mjs
+++ b/packages/runs/src/state.mjs
@@ -56,7 +56,6 @@ function logEntry(line) {
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return null;
if (Object.keys(entry).some((key) => !LOG_KEYS.includes(key))) return null;
if (!["at", "event", "release", "imageTag"].every((key) => typeof entry[key] === "string")) return null;
- if (entry.note !== undefined && typeof entry.note !== "string") return null;
return entry;
}
@@ -0,0 +1,97 @@
✔ isRunId accepts the run id shape and nothing else (1.868216ms)
✔ a missing data root or runs directory lists nothing (2.86021ms)
✔ runs as a regular file lists nothing, as before (8.40054ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.308516ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (12.488337ms)
✔ readRunRecord returns documents and artifacts in directory order (2.16147ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.338434ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.147922ms)
✔ readRunDocument reads only the three run documents (0.986952ms)
✔ a link inside the data root is followed (1.887611ms)
✔ a data root that is itself a link is trusted as configured (1.157689ms)
✔ a run directory linked out of the data root is never read (1.147918ms)
✔ a document linked out of the data root reads as null (1.04803ms)
✔ a runs directory linked out of the data root refuses (0.885659ms)
✔ a relative link that climbs out of the data root refuses (0.747769ms)
✔ a sibling whose name starts with the data root's name is outside it (0.85256ms)
✔ a link loop refuses instead of reading as missing (0.911623ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.49244ms)
✔ an unreadable run directory refuses instead of reading as missing (0.674212ms)
✔ a link to the data root's parent is outside it (0.66393ms)
✔ listRunIds sorts whatever order the directory returns (1.041093ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.720282ms)
✔ the readers write nothing (2.783068ms)
✔ no pointer is null (3.212571ms)
✔ the pointer release.sh writes reads back (8.322256ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (20.560714ms)
✔ a pointer that is a directory refuses with exit 4 (1.915186ms)
✔ a state file linked out of the data root refuses (3.438269ms)
✔ a state directory linked out of the data root refuses (0.97389ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.862097ms)
✔ a missing log is empty (0.580293ms)
✖ the log reads oldest first and counts malformed lines (3.096371ms)
✔ last must be a positive integer (0.860788ms)
✔ the state readers write nothing (1.568966ms)
✔ list prints each run in the established format (103.556364ms)
✔ show prints the run in the established format (99.367753ms)
✔ show of a missing run and an invalid id exit 4 as before (132.794709ms)
✔ list and show refuse a runs directory linked out of the data root (165.176683ms)
✔ show refuses a run linked out of the data root; list reports it unknown (179.243689ms)
✔ list shows an r- name that isn't a valid run id, as before (97.090709ms)
✔ show refuses an invalid id before reading the config (45.621371ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 892.03822
✖ failing tests:
test at packages/runs/tests/state.test.mjs:87:1
✖ the log reads oldest first and counts malformed lines (3.096371ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
{
entries: [
{
at: '2026-09-03T20:57:00Z',
event: 'package',
...
{
+ at: '2026-09-03T20:57:47Z',
+ event: 'activate',
+ imageTag: 'mosaic-poc-agent:0.84.4-r0.0.12',
+ note: null,
+ release: '0.0.12'
+ },
+ {
at: '2026-09-03T20:57:50Z',
event: 'refused',
imageTag: 'mosaic-poc-agent:0.84.4-r0.0.11',
note: 'health check failed (fault-injected)',
release: '0.0.11'
...
],
+ malformed: 6
- malformed: 7
}
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/state.test.mjs:110:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: { entries: [ [Object], [Object], [Object], [Object], [Object] ], malformed: 6 },
expected: { entries: [ [Object], [Object], [Object], [Object] ], malformed: 7 },
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/runs.mjs b/packages/runs/src/runs.mjs
index 03730c33..e02fea8e 100644
--- a/packages/runs/src/runs.mjs
+++ b/packages/runs/src/runs.mjs
@@ -23,7 +23,7 @@ export function listRunIds(dataRoot) {
try {
names = fs.readdirSync(root);
} catch (error) {
- if (isMissing(error)) return [];
+ return [];
throw new RunsError(`cannot read ${root}: ${error.code ?? error.message}`);
}
return names.filter((name) => name.startsWith("r-")).sort();
@@ -0,0 +1,68 @@
✔ isRunId accepts the run id shape and nothing else (1.15727ms)
✔ a missing data root or runs directory lists nothing (1.956821ms)
✔ runs as a regular file lists nothing, as before (0.653312ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.813695ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (3.027323ms)
✔ readRunRecord returns documents and artifacts in directory order (1.453954ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.14821ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.817061ms)
✔ readRunDocument reads only the three run documents (0.720775ms)
✔ a link inside the data root is followed (1.127818ms)
✔ a data root that is itself a link is trusted as configured (0.797552ms)
✔ a run directory linked out of the data root is never read (0.790744ms)
✔ a document linked out of the data root reads as null (1.158428ms)
✔ a runs directory linked out of the data root refuses (0.708719ms)
✔ a relative link that climbs out of the data root refuses (0.456699ms)
✔ a sibling whose name starts with the data root's name is outside it (0.530538ms)
✔ a link loop refuses instead of reading as missing (0.815137ms)
✖ an unreadable runs directory refuses instead of listing nothing (1.594931ms)
✔ an unreadable run directory refuses instead of reading as missing (0.811632ms)
✔ a link to the data root's parent is outside it (0.524666ms)
✔ listRunIds sorts whatever order the directory returns (0.997613ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.610554ms)
✔ the readers write nothing (3.04337ms)
✔ no pointer is null (2.483326ms)
✔ the pointer release.sh writes reads back (1.671155ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (2.225357ms)
✔ a pointer that is a directory refuses with exit 4 (0.654324ms)
✔ a state file linked out of the data root refuses (2.682094ms)
✔ a state directory linked out of the data root refuses (0.672495ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.52315ms)
✔ a missing log is empty (0.407223ms)
✔ the log reads oldest first and counts malformed lines (1.064471ms)
✔ last must be a positive integer (0.626993ms)
✔ the state readers write nothing (1.360837ms)
✔ list prints each run in the established format (93.09785ms)
✔ show prints the run in the established format (76.906223ms)
✔ show of a missing run and an invalid id exit 4 as before (134.282509ms)
✔ list and show refuse a runs directory linked out of the data root (173.332729ms)
✔ show refuses a run linked out of the data root; list reports it unknown (176.342642ms)
✔ list shows an r- name that isn't a valid run id, as before (102.946334ms)
✔ show refuses an invalid id before reading the config (57.272253ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 877.786601
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:177:1
✖ an unreadable runs directory refuses instead of listing nothing (1.594931ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:183:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/runs/src/state.mjs b/packages/runs/src/state.mjs
index af5cc927..ea5b6e96 100644
--- a/packages/runs/src/state.mjs
+++ b/packages/runs/src/state.mjs
@@ -18,6 +18,7 @@ function readStateFile(dataRoot, name) {
try {
return { file, text: fs.readFileSync(file, "utf8") };
} catch (error) {
+ return null;
throw new RunsError(`cannot read ${file}: ${error.code ?? error.message}`);
}
}
@@ -0,0 +1,68 @@
✔ isRunId accepts the run id shape and nothing else (1.695864ms)
✔ a missing data root or runs directory lists nothing (2.111524ms)
✔ runs as a regular file lists nothing, as before (0.687688ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.876875ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (3.457534ms)
✔ readRunRecord returns documents and artifacts in directory order (1.739748ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.186074ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.159887ms)
✔ readRunDocument reads only the three run documents (1.675916ms)
✔ a link inside the data root is followed (1.744427ms)
✔ a data root that is itself a link is trusted as configured (1.031912ms)
✔ a run directory linked out of the data root is never read (1.011737ms)
✔ a document linked out of the data root reads as null (1.210562ms)
✔ a runs directory linked out of the data root refuses (0.875407ms)
✔ a relative link that climbs out of the data root refuses (0.538894ms)
✔ a sibling whose name starts with the data root's name is outside it (0.616345ms)
✔ a link loop refuses instead of reading as missing (1.019959ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.963002ms)
✔ an unreadable run directory refuses instead of reading as missing (0.667147ms)
✔ a link to the data root's parent is outside it (0.500073ms)
✔ listRunIds sorts whatever order the directory returns (0.863784ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.689777ms)
✔ the readers write nothing (2.395707ms)
✔ no pointer is null (1.987192ms)
✔ the pointer release.sh writes reads back (1.500792ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (4.484566ms)
✖ a pointer that is a directory refuses with exit 4 (1.455589ms)
✔ a state file linked out of the data root refuses (1.342733ms)
✔ a state directory linked out of the data root refuses (0.904937ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.979319ms)
✔ a missing log is empty (0.601833ms)
✔ the log reads oldest first and counts malformed lines (1.626737ms)
✔ last must be a positive integer (0.825438ms)
✔ the state readers write nothing (0.984388ms)
✔ list prints each run in the established format (103.938354ms)
✔ show prints the run in the established format (99.605055ms)
✔ show of a missing run and an invalid id exit 4 as before (141.552513ms)
✔ list and show refuse a runs directory linked out of the data root (263.632285ms)
✔ show refuses a run linked out of the data root; list reports it unknown (241.004316ms)
✔ list shows an r- name that isn't a valid run id, as before (92.044439ms)
✔ show refuses an invalid id before reading the config (48.767538ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1064.094476
✖ failing tests:
test at packages/runs/tests/state.test.mjs:51:1
✖ a pointer that is a directory refuses with exit 4 (1.455589ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/state.test.mjs:54:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/state.mjs b/packages/runs/src/state.mjs
index af5cc927..a73160bf 100644
--- a/packages/runs/src/state.mjs
+++ b/packages/runs/src/state.mjs
@@ -37,7 +37,7 @@ export function readActivePointer(dataRoot) {
const invalid = (why) => new RunsError(`release pointer ${why} (${state.file})`, 2);
if (typeof pointer !== "object" || pointer === null || Array.isArray(pointer)) throw invalid("must be a JSON object");
for (const key of Object.keys(pointer)) {
- if (!POINTER_KEYS.includes(key)) throw invalid(`has an unsupported key: "${key}"`);
+ if (false) throw invalid(`has an unsupported key: "${key}"`);
}
if (pointer.pointerVersion !== 1) throw invalid(`has unsupported pointerVersion ${JSON.stringify(pointer.pointerVersion)}`);
for (const key of ["release", "imageTag", "activatedAt"]) {
@@ -0,0 +1,68 @@
✔ isRunId accepts the run id shape and nothing else (2.597935ms)
✔ a missing data root or runs directory lists nothing (7.197028ms)
✔ runs as a regular file lists nothing, as before (0.660483ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.967792ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.465777ms)
✔ readRunRecord returns documents and artifacts in directory order (1.16948ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.822948ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.725039ms)
✔ readRunDocument reads only the three run documents (0.682165ms)
✔ a link inside the data root is followed (1.147589ms)
✔ a data root that is itself a link is trusted as configured (0.764645ms)
✔ a run directory linked out of the data root is never read (0.777721ms)
✔ a document linked out of the data root reads as null (0.934673ms)
✔ a runs directory linked out of the data root refuses (0.660635ms)
✔ a relative link that climbs out of the data root refuses (0.494374ms)
✔ a sibling whose name starts with the data root's name is outside it (0.543282ms)
✔ a link loop refuses instead of reading as missing (0.711437ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.727803ms)
✔ an unreadable run directory refuses instead of reading as missing (0.685592ms)
✔ a link to the data root's parent is outside it (0.407671ms)
✔ listRunIds sorts whatever order the directory returns (4.167937ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.916379ms)
✔ the readers write nothing (3.641306ms)
✔ no pointer is null (5.346721ms)
✔ the pointer release.sh writes reads back (1.585894ms)
✖ a pointer that isn't the version 1 shape refuses with exit 2 (28.811661ms)
✔ a pointer that is a directory refuses with exit 4 (1.063525ms)
✔ a state file linked out of the data root refuses (1.952364ms)
✔ a state directory linked out of the data root refuses (0.628002ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.543924ms)
✔ a missing log is empty (3.99435ms)
✔ the log reads oldest first and counts malformed lines (1.194387ms)
✔ last must be a positive integer (0.54103ms)
✔ the state readers write nothing (1.220604ms)
✔ list prints each run in the established format (94.412961ms)
✔ show prints the run in the established format (121.913576ms)
✔ show of a missing run and an invalid id exit 4 as before (161.486907ms)
✔ list and show refuse a runs directory linked out of the data root (220.557107ms)
✔ show refuses a run linked out of the data root; list reports it unknown (192.17414ms)
✔ list shows an r- name that isn't a valid run id, as before (106.896964ms)
✔ show refuses an invalid id before reading the config (58.262537ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1040.073192
✖ failing tests:
test at packages/runs/tests/state.test.mjs:32:1
✖ a pointer that isn't the version 1 shape refuses with exit 2 (28.811661ms)
AssertionError [ERR_ASSERTION]: Missing expected exception: /unsupported key: "extra"/
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/state.test.mjs:47:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/paths.mjs b/packages/runs/src/paths.mjs
index 1dbb5539..fe56cb9b 100644
--- a/packages/runs/src/paths.mjs
+++ b/packages/runs/src/paths.mjs
@@ -11,7 +11,7 @@ export function isMissing(error) {
function isInside(root, target) {
const relative = path.relative(root, target);
- return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
+ return false || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
}
// Resolves <dataRoot>/<parts...> through any symbolic links and returns the
@@ -0,0 +1,49 @@
✔ isRunId accepts the run id shape and nothing else (1.6626ms)
✔ a missing data root or runs directory lists nothing (2.557802ms)
✔ runs as a regular file lists nothing, as before (0.993455ms)
✔ listRunIds keeps r- names only, sorted oldest first (2.10845ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.526378ms)
✔ readRunRecord returns documents and artifacts in directory order (1.74364ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (3.435233ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.009532ms)
✔ readRunDocument reads only the three run documents (0.836393ms)
✔ a link inside the data root is followed (1.471449ms)
✔ a data root that is itself a link is trusted as configured (4.897625ms)
✔ a run directory linked out of the data root is never read (1.428292ms)
✔ a document linked out of the data root reads as null (1.498822ms)
✔ a runs directory linked out of the data root refuses (0.935108ms)
✔ a relative link that climbs out of the data root refuses (0.709396ms)
✔ a sibling whose name starts with the data root's name is outside it (0.882846ms)
✔ a link loop refuses instead of reading as missing (1.058619ms)
✔ an unreadable runs directory refuses instead of listing nothing (2.11653ms)
✔ an unreadable run directory refuses instead of reading as missing (0.930758ms)
✔ a link to the data root's parent is outside it (1.598177ms)
✔ listRunIds sorts whatever order the directory returns (1.288405ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.870374ms)
✔ the readers write nothing (3.744027ms)
✔ no pointer is null (2.085388ms)
✔ the pointer release.sh writes reads back (5.493014ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (15.128508ms)
✔ a pointer that is a directory refuses with exit 4 (5.23987ms)
✔ a state file linked out of the data root refuses (1.810665ms)
✔ a state directory linked out of the data root refuses (0.998991ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.803279ms)
✔ a missing log is empty (0.565342ms)
✔ the log reads oldest first and counts malformed lines (1.636209ms)
✔ last must be a positive integer (0.735553ms)
✔ the state readers write nothing (2.258054ms)
✔ list prints each run in the established format (116.9074ms)
✔ show prints the run in the established format (92.239762ms)
✔ show of a missing run and an invalid id exit 4 as before (138.161653ms)
✔ list and show refuse a runs directory linked out of the data root (180.575601ms)
✔ show refuses a run linked out of the data root; list reports it unknown (155.863719ms)
✔ list shows an r- name that isn't a valid run id, as before (66.034048ms)
✔ show refuses an invalid id before reading the config (45.978715ms)
ℹ tests 41
ℹ suites 0
ℹ pass 41
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 873.417164
@@ -0,0 +1,13 @@
diff --git a/scripts/mosaic-task.mjs b/scripts/mosaic-task.mjs
index cf8399a0..7c4bfb80 100755
--- a/scripts/mosaic-task.mjs
+++ b/scripts/mosaic-task.mjs
@@ -462,7 +462,7 @@ function readRuns(read) {
try {
return read();
} catch (error) {
- if (error instanceof RunsError) fail(error.exitCode, error.message);
+ if (false) fail(error.exitCode, error.message);
throw error;
}
}
@@ -0,0 +1,93 @@
✔ isRunId accepts the run id shape and nothing else (1.160098ms)
✔ a missing data root or runs directory lists nothing (1.712897ms)
✔ runs as a regular file lists nothing, as before (0.650182ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.113564ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.406922ms)
✔ readRunRecord returns documents and artifacts in directory order (1.572189ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.777226ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.754688ms)
✔ readRunDocument reads only the three run documents (0.682826ms)
✔ a link inside the data root is followed (1.118588ms)
✔ a data root that is itself a link is trusted as configured (0.860124ms)
✔ a run directory linked out of the data root is never read (1.212714ms)
✔ a document linked out of the data root reads as null (1.539772ms)
✔ a runs directory linked out of the data root refuses (0.792065ms)
✔ a relative link that climbs out of the data root refuses (0.634973ms)
✔ a sibling whose name starts with the data root's name is outside it (0.819447ms)
✔ a link loop refuses instead of reading as missing (1.025891ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.73579ms)
✔ an unreadable run directory refuses instead of reading as missing (0.654308ms)
✔ a link to the data root's parent is outside it (0.431647ms)
✔ listRunIds sorts whatever order the directory returns (0.90279ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.544152ms)
✔ the readers write nothing (2.666725ms)
✔ no pointer is null (1.95949ms)
✔ the pointer release.sh writes reads back (1.316236ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (1.881776ms)
✔ a pointer that is a directory refuses with exit 4 (1.406961ms)
✔ a state file linked out of the data root refuses (1.07326ms)
✔ a state directory linked out of the data root refuses (0.649188ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.583015ms)
✔ a missing log is empty (0.436768ms)
✔ the log reads oldest first and counts malformed lines (1.10825ms)
✔ last must be a positive integer (0.518269ms)
✔ the state readers write nothing (0.908431ms)
✔ list prints each run in the established format (88.182372ms)
✔ show prints the run in the established format (90.40074ms)
✔ show of a missing run and an invalid id exit 4 as before (163.139175ms)
✖ list and show refuse a runs directory linked out of the data root (86.669533ms)
✖ show refuses a run linked out of the data root; list reports it unknown (87.589376ms)
✔ list shows an r- name that isn't a valid run id, as before (78.640786ms)
✔ show refuses an invalid id before reading the config (41.75937ms)
ℹ tests 41
ℹ suites 0
ℹ pass 39
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 697.746994
✖ failing tests:
test at packages/runs/tests/task-cli.test.mjs:70:1
✖ list and show refuse a runs directory linked out of the data root (86.669533ms)
AssertionError [ERR_ASSERTION]: list
1 !== 4
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/task-cli.test.mjs:76:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 1,
expected: 4,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/runs/tests/task-cli.test.mjs:82:1
✖ show refuses a run linked out of the data root; list reports it unknown (87.589376ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
1 !== 4
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/task-cli.test.mjs:88:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 1,
expected: 4,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/runs/src/runs.mjs b/packages/runs/src/runs.mjs
index 03730c33..943dadfa 100644
--- a/packages/runs/src/runs.mjs
+++ b/packages/runs/src/runs.mjs
@@ -59,7 +59,6 @@ export function readRunRecord(dataRoot, runId) {
try {
artifacts = fs.readdirSync(dir);
} catch (error) {
- if (isMissing(error)) return null;
throw new RunsError(`cannot read ${dir}: ${error.code ?? error.message}`);
}
return {
@@ -0,0 +1,65 @@
✔ isRunId accepts the run id shape and nothing else (1.019792ms)
✔ a missing data root or runs directory lists nothing (1.537162ms)
✔ runs as a regular file lists nothing, as before (0.626401ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.70396ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (1.605381ms)
✔ readRunRecord returns documents and artifacts in directory order (0.978894ms)
✖ readRunRecord is null for a missing run, a dangling link or a file (0.782736ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.618964ms)
✔ readRunDocument reads only the three run documents (0.639582ms)
✔ a link inside the data root is followed (0.953467ms)
✔ a data root that is itself a link is trusted as configured (0.675656ms)
✔ a run directory linked out of the data root is never read (0.689555ms)
✔ a document linked out of the data root reads as null (0.894036ms)
✔ a runs directory linked out of the data root refuses (0.600981ms)
✔ a relative link that climbs out of the data root refuses (0.422303ms)
✔ a sibling whose name starts with the data root's name is outside it (0.542892ms)
✔ a link loop refuses instead of reading as missing (0.66723ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.512036ms)
✔ an unreadable run directory refuses instead of reading as missing (0.626632ms)
✔ a link to the data root's parent is outside it (0.403352ms)
✔ listRunIds sorts whatever order the directory returns (0.81109ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.63346ms)
✔ the readers write nothing (2.24943ms)
✔ no pointer is null (1.762831ms)
✔ the pointer release.sh writes reads back (1.161502ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (1.684703ms)
✔ a pointer that is a directory refuses with exit 4 (1.066646ms)
✔ a state file linked out of the data root refuses (0.95879ms)
✔ a state directory linked out of the data root refuses (0.598971ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.443066ms)
✔ a missing log is empty (0.346331ms)
✔ the log reads oldest first and counts malformed lines (0.900263ms)
✔ last must be a positive integer (0.433079ms)
✔ the state readers write nothing (0.791434ms)
✔ list prints each run in the established format (78.266364ms)
✔ show prints the run in the established format (69.618235ms)
✔ show of a missing run and an invalid id exit 4 as before (112.18599ms)
✔ list and show refuse a runs directory linked out of the data root (147.542974ms)
✔ show refuses a run linked out of the data root; list reports it unknown (133.430679ms)
✔ list shows an r- name that isn't a valid run id, as before (76.660284ms)
✔ show refuses an invalid id before reading the config (45.844203ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 718.130278
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:75:1
✖ readRunRecord is null for a missing run, a dangling link or a file (0.782736ms)
Error [RunsError]: cannot read /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-b3hgCt/data/runs/r-20260101T000100Z-bbbbbb: ENOTDIR
at readRunRecord (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:62:11)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:83:16)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
exitCode: 4
}
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/paths.mjs b/packages/runs/src/paths.mjs
index 1dbb5539..7ce9d52c 100644
--- a/packages/runs/src/paths.mjs
+++ b/packages/runs/src/paths.mjs
@@ -24,7 +24,7 @@ export function resolveInside(dataRoot, ...parts) {
let real;
try {
root = fs.realpathSync(dataRoot);
- real = fs.realpathSync(target);
+ real = path.resolve(target);
} catch (error) {
if (isMissing(error)) return null;
throw new RunsError(`cannot resolve ${target}: ${error.code ?? error.message}`);
@@ -0,0 +1,353 @@
✔ isRunId accepts the run id shape and nothing else (1.054784ms)
✔ a missing data root or runs directory lists nothing (1.652336ms)
✔ runs as a regular file lists nothing, as before (0.551128ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.225706ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (1.355194ms)
✔ readRunRecord returns documents and artifacts in directory order (0.964287ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.660858ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.691905ms)
✔ readRunDocument reads only the three run documents (0.56909ms)
✔ a link inside the data root is followed (0.880389ms)
✖ a data root that is itself a link is trusted as configured (0.699319ms)
✖ a run directory linked out of the data root is never read (1.673175ms)
✖ a document linked out of the data root reads as null (0.958721ms)
✖ a runs directory linked out of the data root refuses (0.510659ms)
✖ a relative link that climbs out of the data root refuses (0.603903ms)
✖ a sibling whose name starts with the data root's name is outside it (0.702796ms)
✔ a link loop refuses instead of reading as missing (0.614909ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.453137ms)
✔ an unreadable run directory refuses instead of reading as missing (0.602137ms)
✖ a link to the data root's parent is outside it (0.550265ms)
✔ listRunIds sorts whatever order the directory returns (0.752446ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.486875ms)
✔ the readers write nothing (2.072302ms)
✖ no pointer is null (1.751412ms)
✔ the pointer release.sh writes reads back (1.046095ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (1.648482ms)
✔ a pointer that is a directory refuses with exit 4 (0.585862ms)
✖ a state file linked out of the data root refuses (1.759557ms)
✖ a state directory linked out of the data root refuses (0.604972ms)
✖ a state directory linked out to nothing reads as no release and an empty log (0.425196ms)
✖ a missing log is empty (0.475951ms)
✔ the log reads oldest first and counts malformed lines (0.961794ms)
✔ last must be a positive integer (0.506807ms)
✔ the state readers write nothing (0.83569ms)
✔ list prints each run in the established format (82.854009ms)
✔ show prints the run in the established format (77.561091ms)
✔ show of a missing run and an invalid id exit 4 as before (119.793186ms)
✖ list and show refuse a runs directory linked out of the data root (92.256203ms)
✖ show refuses a run linked out of the data root; list reports it unknown (80.040594ms)
✔ list shows an r- name that isn't a valid run id, as before (72.307362ms)
✔ show refuses an invalid id before reading the config (47.222817ms)
ℹ tests 41
ℹ suites 0
ℹ pass 27
ℹ fail 14
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 641.071641
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:111:1
✖ a data root that is itself a link is trusted as configured (0.699319ms)
Error [RunsError]: /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-J1fCqk/alias/runs resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-J1fCqk/data)
at resolveInside (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/paths.mjs:33:11)
at listRunIds (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:20:16)
at listRunRecords (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:44:10)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:116:20)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
exitCode: 4
}
test at packages/runs/tests/runs.test.mjs:119:1
✖ a run directory linked out of the data root is never read (1.673175ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
{
+ result: {
+ durationMs: 1000,
+ exitCode: 0,
+ expectedExact: null,
+ finishedAt: '2026-01-01T00:00:01.000Z',
+ missionId: null,
+ model: 'm',
+ provider: 'zai',
+ reason: null,
+ request: 'hi',
+ response: 'hi',
+ runId: 'r-20260101T000000Z-aaaaaa',
+ runVersion: 1,
+ session: null,
+ sessionForkFrom: null,
+ signal: null,
+ startedAt: '2026-01-01T00:00:00.000Z',
+ status: 'succeeded',
+ taskId: 't-one',
+ tools: null,
+ workspace: null
+ },
- result: null,
runId: 'r-20260101T000000Z-aaaaaa'
}
]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:125:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [ { runId: 'r-20260101T000000Z-aaaaaa', result: [Object] } ],
expected: [ { runId: 'r-20260101T000000Z-aaaaaa', result: null } ],
operator: 'deepStrictEqual',
diff: 'simple'
}
test at packages/runs/tests/runs.test.mjs:129:1
✖ a document linked out of the data root reads as null (0.958721ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ {
+ durationMs: 1000,
+ exitCode: 0,
+ expectedExact: null,
+ finishedAt: '2026-01-01T00:00:01.000Z',
+ missionId: null,
+ model: 'm',
+ provider: 'zai',
+ reason: null,
+ request: 'hi',
+ response: 'hi',
+ runId: 'r-20260101T000000Z-aaaaaa',
+ runVersion: 1,
+ session: null,
+ sessionForkFrom: null,
+ signal: null,
+ startedAt: '2026-01-01T00:00:00.000Z',
+ status: 'succeeded',
+ taskId: 't-one',
+ tools: null,
+ workspace: null
+ }
- null
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:137:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: { runVersion: 1, runId: 'r-20260101T000000Z-aaaaaa', taskId: 't-one', missionId: null, status: 'succeeded', reason: null, request: 'hi', response: 'hi', expectedExact: null, workspace: null, tools: null, session: null, sessionForkFrom: null, exitCode: 0, signal: null, provider: 'zai', model: 'm', startedAt: '2026-01-01T00:00:00.000Z', finishedAt: '2026-01-01T00:00:01.000Z', durationMs: 1000 },
expected: null,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/runs/tests/runs.test.mjs:143:1
✖ a runs directory linked out of the data root refuses (0.510659ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:147:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /runs resolves outside the data root/,
operator: 'throws',
diff: 'simple'
}
test at packages/runs/tests/runs.test.mjs:152:1
✖ a relative link that climbs out of the data root refuses (0.603903ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:156:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /resolves outside the data root/,
operator: 'throws',
diff: 'simple'
}
test at packages/runs/tests/runs.test.mjs:159:1
✖ a sibling whose name starts with the data root's name is outside it (0.702796ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:165:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /resolves outside the data root/,
operator: 'throws',
diff: 'simple'
}
test at packages/runs/tests/runs.test.mjs:201:1
✖ a link to the data root's parent is outside it (0.550265ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:205:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /resolves outside the data root/,
operator: 'throws',
diff: 'simple'
}
test at packages/runs/tests/state.test.mjs:18:1
✖ no pointer is null (1.751412ms)
Error [RunsError]: cannot read /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-Hvm8Hi/data/state/active.json: ENOENT
at readStateFile (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/state.mjs:21:11)
at readActivePointer (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/state.mjs:29:17)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/state.test.mjs:20:16)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
exitCode: 4
}
test at packages/runs/tests/state.test.mjs:57:1
✖ a state file linked out of the data root refuses (1.759557ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/state.test.mjs:64:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /resolves outside the data root/,
operator: 'throws',
diff: 'simple'
}
test at packages/runs/tests/state.test.mjs:68:1
✖ a state directory linked out of the data root refuses (0.604972ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/state.test.mjs:72:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /state\/active.json resolves outside the data root/,
operator: 'throws',
diff: 'simple'
}
test at packages/runs/tests/state.test.mjs:75:1
✖ a state directory linked out to nothing reads as no release and an empty log (0.425196ms)
Error [RunsError]: cannot read /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-SAMKL6/data/state/active.json: ENOENT
at readStateFile (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/state.mjs:21:11)
at readActivePointer (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/state.mjs:29:17)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/state.test.mjs:78:16)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
exitCode: 4
}
test at packages/runs/tests/state.test.mjs:82:1
✖ a missing log is empty (0.475951ms)
Error [RunsError]: cannot read /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-YDcaud/data/state/activation-log.jsonl: ENOENT
at readStateFile (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/state.mjs:21:11)
at readActivationLog (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/state.mjs:72:17)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/state.test.mjs:84:20)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
exitCode: 4
}
test at packages/runs/tests/task-cli.test.mjs:70:1
✖ list and show refuse a runs directory linked out of the data root (92.256203ms)
AssertionError [ERR_ASSERTION]: list
0 !== 4
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/task-cli.test.mjs:76:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 0,
expected: 4,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/runs/tests/task-cli.test.mjs:82:1
✖ show refuses a run linked out of the data root; list reports it unknown (80.040594ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 4
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/task-cli.test.mjs:88:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 4,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/paths.mjs b/packages/runs/src/paths.mjs
index 1dbb5539..2cbffca6 100644
--- a/packages/runs/src/paths.mjs
+++ b/packages/runs/src/paths.mjs
@@ -23,7 +23,7 @@ export function resolveInside(dataRoot, ...parts) {
let root;
let real;
try {
- root = fs.realpathSync(dataRoot);
+ root = path.resolve(dataRoot);
real = fs.realpathSync(target);
} catch (error) {
if (isMissing(error)) return null;
@@ -0,0 +1,67 @@
✔ isRunId accepts the run id shape and nothing else (0.997986ms)
✔ a missing data root or runs directory lists nothing (1.607466ms)
✔ runs as a regular file lists nothing, as before (0.66625ms)
✔ listRunIds keeps r- names only, sorted oldest first (2.247012ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.325328ms)
✔ readRunRecord returns documents and artifacts in directory order (1.518989ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.91467ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.688117ms)
✔ readRunDocument reads only the three run documents (0.679207ms)
✔ a link inside the data root is followed (1.094892ms)
✖ a data root that is itself a link is trusted as configured (0.717025ms)
✔ a run directory linked out of the data root is never read (0.971395ms)
✔ a document linked out of the data root reads as null (1.253566ms)
✔ a runs directory linked out of the data root refuses (0.793626ms)
✔ a relative link that climbs out of the data root refuses (0.713461ms)
✔ a sibling whose name starts with the data root's name is outside it (0.825603ms)
✔ a link loop refuses instead of reading as missing (0.703219ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.451785ms)
✔ an unreadable run directory refuses instead of reading as missing (0.578206ms)
✔ a link to the data root's parent is outside it (0.386068ms)
✔ listRunIds sorts whatever order the directory returns (0.739358ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.531107ms)
✔ the readers write nothing (1.843235ms)
✔ no pointer is null (1.972129ms)
✔ the pointer release.sh writes reads back (1.24273ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (1.770283ms)
✔ a pointer that is a directory refuses with exit 4 (0.565257ms)
✔ a state file linked out of the data root refuses (1.498773ms)
✔ a state directory linked out of the data root refuses (0.660361ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.59035ms)
✔ a missing log is empty (0.482079ms)
✔ the log reads oldest first and counts malformed lines (1.084198ms)
✔ last must be a positive integer (0.634979ms)
✔ the state readers write nothing (0.860207ms)
✔ list prints each run in the established format (87.530195ms)
✔ show prints the run in the established format (78.052145ms)
✔ show of a missing run and an invalid id exit 4 as before (114.872261ms)
✔ list and show refuse a runs directory linked out of the data root (200.792804ms)
✔ show refuses a run linked out of the data root; list reports it unknown (194.493903ms)
✔ list shows an r- name that isn't a valid run id, as before (129.049237ms)
✔ show refuses an invalid id before reading the config (48.628287ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 914.584975
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:111:1
✖ a data root that is itself a link is trusted as configured (0.717025ms)
Error [RunsError]: /home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-ZbqJkI/alias/runs resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/tmp/mosaic-runs-test-ZbqJkI/alias)
at resolveInside (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/paths.mjs:33:11)
at listRunIds (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:20:16)
at listRunRecords (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:44:10)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:116:20)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
exitCode: 4
}
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/runs.mjs b/packages/runs/src/runs.mjs
index 03730c33..08a71fab 100644
--- a/packages/runs/src/runs.mjs
+++ b/packages/runs/src/runs.mjs
@@ -2,7 +2,7 @@ import fs from "node:fs";
import { RunsError } from "./errors.mjs";
import { RUNS_DIRNAME, isMissing, readJsonObject, resolveInside } from "./paths.mjs";
-export const RUN_ID_PATTERN = /^r-[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
+export const RUN_ID_PATTERN = /^r-[A-Za-z0-9][A-Za-z0-9._-]{0,64}$/;
export const RUN_DOCUMENTS = ["result.json", "task.json", "mission.json"];
export function isRunId(value) {
@@ -0,0 +1,70 @@
✖ isRunId accepts the run id shape and nothing else (2.210226ms)
✔ a missing data root or runs directory lists nothing (2.603108ms)
✔ runs as a regular file lists nothing, as before (0.935946ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.163969ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.413922ms)
✔ readRunRecord returns documents and artifacts in directory order (1.371064ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.118632ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.780695ms)
✔ readRunDocument reads only the three run documents (0.621037ms)
✔ a link inside the data root is followed (1.177301ms)
✔ a data root that is itself a link is trusted as configured (0.764799ms)
✔ a run directory linked out of the data root is never read (0.722632ms)
✔ a document linked out of the data root reads as null (0.995435ms)
✔ a runs directory linked out of the data root refuses (0.710336ms)
✔ a relative link that climbs out of the data root refuses (0.470174ms)
✔ a sibling whose name starts with the data root's name is outside it (0.553622ms)
✔ a link loop refuses instead of reading as missing (1.050635ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.578181ms)
✔ an unreadable run directory refuses instead of reading as missing (0.593413ms)
✔ a link to the data root's parent is outside it (0.486943ms)
✔ listRunIds sorts whatever order the directory returns (0.94863ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.588248ms)
✔ the readers write nothing (5.231385ms)
✔ no pointer is null (2.354597ms)
✔ the pointer release.sh writes reads back (1.773315ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (2.415432ms)
✔ a pointer that is a directory refuses with exit 4 (1.450113ms)
✔ a state file linked out of the data root refuses (1.244576ms)
✔ a state directory linked out of the data root refuses (0.652045ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.621644ms)
✔ a missing log is empty (0.549717ms)
✔ the log reads oldest first and counts malformed lines (1.224628ms)
✔ last must be a positive integer (0.607716ms)
✔ the state readers write nothing (1.009192ms)
✔ list prints each run in the established format (89.320943ms)
✔ show prints the run in the established format (94.517441ms)
✔ show of a missing run and an invalid id exit 4 as before (144.124028ms)
✔ list and show refuse a runs directory linked out of the data root (170.44307ms)
✔ show refuses a run linked out of the data root; list reports it unknown (157.310275ms)
✔ list shows an r- name that isn't a valid run id, as before (89.274742ms)
✔ show refuses an invalid id before reading the config (45.834389ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 861.630104
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:13:1
✖ isRunId accepts the run id shape and nothing else (2.210226ms)
AssertionError [ERR_ASSERTION]: r-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
true !== false
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:16:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: true,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/runs.mjs b/packages/runs/src/runs.mjs
index 03730c33..db7d57c1 100644
--- a/packages/runs/src/runs.mjs
+++ b/packages/runs/src/runs.mjs
@@ -43,7 +43,7 @@ export function readRunDocument(dataRoot, runId, name) {
export function listRunRecords(dataRoot) {
return listRunIds(dataRoot).map((runId) => ({
runId,
- result: readJsonObject(dataRoot, RUNS_DIRNAME, runId, "result.json"),
+ result: readRunDocument(dataRoot, runId, "result.json"),
}));
}
@@ -0,0 +1,89 @@
✔ isRunId accepts the run id shape and nothing else (1.590741ms)
✔ a missing data root or runs directory lists nothing (2.330311ms)
✔ runs as a regular file lists nothing, as before (0.907679ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.181581ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.54764ms)
✔ readRunRecord returns documents and artifacts in directory order (1.169101ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.818664ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.787166ms)
✔ readRunDocument reads only the three run documents (0.619749ms)
✔ a link inside the data root is followed (1.130548ms)
✔ a data root that is itself a link is trusted as configured (0.756907ms)
✔ a run directory linked out of the data root is never read (1.079323ms)
✔ a document linked out of the data root reads as null (1.034215ms)
✔ a runs directory linked out of the data root refuses (0.684294ms)
✔ a relative link that climbs out of the data root refuses (0.532266ms)
✔ a sibling whose name starts with the data root's name is outside it (0.611387ms)
✔ a link loop refuses instead of reading as missing (0.723085ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.536617ms)
✔ an unreadable run directory refuses instead of reading as missing (0.640848ms)
✔ a link to the data root's parent is outside it (0.451118ms)
✔ listRunIds sorts whatever order the directory returns (0.968119ms)
✖ listRunRecords lists an r- name that isn't a valid run id, as before (0.6552ms)
✔ the readers write nothing (2.352082ms)
✔ no pointer is null (3.071243ms)
✔ the pointer release.sh writes reads back (2.029825ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (37.796406ms)
✔ a pointer that is a directory refuses with exit 4 (0.729401ms)
✔ a state file linked out of the data root refuses (1.672903ms)
✔ a state directory linked out of the data root refuses (0.622978ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.586589ms)
✔ a missing log is empty (0.450358ms)
✔ the log reads oldest first and counts malformed lines (1.135566ms)
✔ last must be a positive integer (0.515257ms)
✔ the state readers write nothing (0.965261ms)
✔ list prints each run in the established format (100.007368ms)
✔ show prints the run in the established format (76.759238ms)
✔ show of a missing run and an invalid id exit 4 as before (165.294787ms)
✔ list and show refuse a runs directory linked out of the data root (137.384111ms)
✔ show refuses a run linked out of the data root; list reports it unknown (152.698033ms)
✖ list shows an r- name that isn't a valid run id, as before (73.773092ms)
✔ show refuses an invalid id before reading the config (39.617828ms)
ℹ tests 41
ℹ suites 0
ℹ pass 39
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 809.245413
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:220:1
✖ listRunRecords lists an r- name that isn't a valid run id, as before (0.6552ms)
Error [RunsError]: invalid run id: "r-.bad" (expected r-<id>)
at requireRunId (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:13:30)
at readRunDocument (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:35:3)
at file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:46:13
at Array.map (<anonymous>)
at listRunRecords (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/src/runs.mjs:44:31)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:225:20)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19) {
exitCode: 4
}
test at packages/runs/tests/task-cli.test.mjs:96:1
✖ list shows an r- name that isn't a valid run id, as before (73.773092ms)
AssertionError [ERR_ASSERTION]: mosaic-task: invalid run id: "r-.bad" (expected r-<id>)
4 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/task-cli.test.mjs:100:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/state.mjs b/packages/runs/src/state.mjs
index af5cc927..9788bfca 100644
--- a/packages/runs/src/state.mjs
+++ b/packages/runs/src/state.mjs
@@ -74,7 +74,7 @@ export function readActivationLog(dataRoot, { last } = {}) {
const entries = [];
let malformed = 0;
for (const line of state.text.split("\n")) {
- if (line.trim() === "") continue;
+ if (line === "") continue;
const entry = logEntry(line);
if (entry === null) malformed += 1;
else entries.push(entry);
@@ -0,0 +1,83 @@
✔ isRunId accepts the run id shape and nothing else (2.009126ms)
✔ a missing data root or runs directory lists nothing (13.094847ms)
✔ runs as a regular file lists nothing, as before (2.278303ms)
✔ listRunIds keeps r- names only, sorted oldest first (2.327495ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (4.905919ms)
✔ readRunRecord returns documents and artifacts in directory order (7.96623ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (3.918579ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (5.602756ms)
✔ readRunDocument reads only the three run documents (3.521732ms)
✔ a link inside the data root is followed (3.442024ms)
✔ a data root that is itself a link is trusted as configured (2.005087ms)
✔ a run directory linked out of the data root is never read (2.084232ms)
✔ a document linked out of the data root reads as null (7.619635ms)
✔ a runs directory linked out of the data root refuses (1.250289ms)
✔ a relative link that climbs out of the data root refuses (0.818784ms)
✔ a sibling whose name starts with the data root's name is outside it (3.850288ms)
✔ a link loop refuses instead of reading as missing (4.675288ms)
✔ an unreadable runs directory refuses instead of listing nothing (1.954118ms)
✔ an unreadable run directory refuses instead of reading as missing (1.858696ms)
✔ a link to the data root's parent is outside it (1.178473ms)
✔ listRunIds sorts whatever order the directory returns (1.430997ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (3.626875ms)
✔ the readers write nothing (3.631659ms)
✔ no pointer is null (12.722881ms)
✔ the pointer release.sh writes reads back (2.956634ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (23.610773ms)
✔ a pointer that is a directory refuses with exit 4 (2.467786ms)
✔ a state file linked out of the data root refuses (9.154475ms)
✔ a state directory linked out of the data root refuses (5.636323ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.989436ms)
✔ a missing log is empty (0.893401ms)
✖ the log reads oldest first and counts malformed lines (3.734558ms)
✔ last must be a positive integer (0.948278ms)
✔ the state readers write nothing (2.050762ms)
✔ list prints each run in the established format (139.06499ms)
✔ show prints the run in the established format (91.810125ms)
✔ show of a missing run and an invalid id exit 4 as before (268.025853ms)
✔ list and show refuse a runs directory linked out of the data root (205.373671ms)
✔ show refuses a run linked out of the data root; list reports it unknown (196.491701ms)
✔ list shows an r- name that isn't a valid run id, as before (89.089467ms)
✔ show refuses an invalid id before reading the config (49.958959ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1233.510304
✖ failing tests:
test at packages/runs/tests/state.test.mjs:87:1
✖ the log reads oldest first and counts malformed lines (3.734558ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
{
entries: [
{
at: '2026-09-03T20:57:00Z',
event: 'package',
...
],
+ malformed: 8
- malformed: 7
}
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/state.test.mjs:110:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: { entries: [ [Object], [Object], [Object], [Object] ], malformed: 8 },
expected: { entries: [ [Object], [Object], [Object], [Object] ], malformed: 7 },
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/runs/src/runs.mjs b/packages/runs/src/runs.mjs
index 03730c33..d8cb0e58 100644
--- a/packages/runs/src/runs.mjs
+++ b/packages/runs/src/runs.mjs
@@ -32,7 +32,6 @@ export function listRunIds(dataRoot) {
// One of RUN_DOCUMENTS from a run, or null when it is missing, unreadable,
// not a JSON object or outside the data root.
export function readRunDocument(dataRoot, runId, name) {
- requireRunId(runId);
if (!RUN_DOCUMENTS.includes(name)) throw new RunsError(`unknown run document: ${JSON.stringify(name)}`);
return readJsonObject(dataRoot, RUNS_DIRNAME, runId, name);
}
@@ -0,0 +1,68 @@
✔ isRunId accepts the run id shape and nothing else (1.789031ms)
✔ a missing data root or runs directory lists nothing (2.597337ms)
✔ runs as a regular file lists nothing, as before (1.16149ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.463129ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (4.240373ms)
✔ readRunRecord returns documents and artifacts in directory order (2.086403ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.577833ms)
✖ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (2.195478ms)
✔ readRunDocument reads only the three run documents (1.16193ms)
✔ a link inside the data root is followed (2.748711ms)
✔ a data root that is itself a link is trusted as configured (2.929924ms)
✔ a run directory linked out of the data root is never read (1.362183ms)
✔ a document linked out of the data root reads as null (3.606348ms)
✔ a runs directory linked out of the data root refuses (1.731427ms)
✔ a relative link that climbs out of the data root refuses (0.783109ms)
✔ a sibling whose name starts with the data root's name is outside it (0.924617ms)
✔ a link loop refuses instead of reading as missing (1.189822ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.74324ms)
✔ an unreadable run directory refuses instead of reading as missing (1.046894ms)
✔ a link to the data root's parent is outside it (0.803458ms)
✔ listRunIds sorts whatever order the directory returns (1.402974ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.820001ms)
✔ the readers write nothing (5.081678ms)
✔ no pointer is null (3.322821ms)
✔ the pointer release.sh writes reads back (2.043259ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (6.549959ms)
✔ a pointer that is a directory refuses with exit 4 (6.005478ms)
✔ a state file linked out of the data root refuses (4.026755ms)
✔ a state directory linked out of the data root refuses (1.081501ms)
✔ a state directory linked out to nothing reads as no release and an empty log (1.876666ms)
✔ a missing log is empty (0.648509ms)
✔ the log reads oldest first and counts malformed lines (2.286529ms)
✔ last must be a positive integer (0.996248ms)
✔ the state readers write nothing (1.822738ms)
✔ list prints each run in the established format (191.335275ms)
✔ show prints the run in the established format (358.090605ms)
✔ show of a missing run and an invalid id exit 4 as before (647.478452ms)
✔ list and show refuse a runs directory linked out of the data root (725.179038ms)
✔ show refuses a run linked out of the data root; list reports it unknown (595.356633ms)
✔ list shows an r- name that isn't a valid run id, as before (144.877778ms)
✔ show refuses an invalid id before reading the config (138.198732ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2929.86496
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:86:1
✖ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (2.195478ms)
AssertionError [ERR_ASSERTION]: Missing expected exception (RunsError).
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:90:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/runs.mjs b/packages/runs/src/runs.mjs
index 03730c33..3814b6a5 100644
--- a/packages/runs/src/runs.mjs
+++ b/packages/runs/src/runs.mjs
@@ -26,7 +26,7 @@ export function listRunIds(dataRoot) {
if (isMissing(error)) return [];
throw new RunsError(`cannot read ${root}: ${error.code ?? error.message}`);
}
- return names.filter((name) => name.startsWith("r-")).sort();
+ return names.filter(isRunId).sort();
}
// One of RUN_DOCUMENTS from a run, or null when it is missing, unreadable,
@@ -0,0 +1,99 @@
✔ isRunId accepts the run id shape and nothing else (2.349624ms)
✔ a missing data root or runs directory lists nothing (3.06341ms)
✔ runs as a regular file lists nothing, as before (1.261241ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.52865ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (4.80885ms)
✔ readRunRecord returns documents and artifacts in directory order (1.949916ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (3.289521ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.223411ms)
✔ readRunDocument reads only the three run documents (0.817925ms)
✔ a link inside the data root is followed (1.649226ms)
✔ a data root that is itself a link is trusted as configured (1.268864ms)
✔ a run directory linked out of the data root is never read (1.271219ms)
✔ a document linked out of the data root reads as null (4.558659ms)
✔ a runs directory linked out of the data root refuses (1.24361ms)
✔ a relative link that climbs out of the data root refuses (0.769591ms)
✔ a sibling whose name starts with the data root's name is outside it (0.855321ms)
✔ a link loop refuses instead of reading as missing (1.125869ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.92955ms)
✔ an unreadable run directory refuses instead of reading as missing (1.103476ms)
✔ a link to the data root's parent is outside it (0.933244ms)
✔ listRunIds sorts whatever order the directory returns (1.548586ms)
✖ listRunRecords lists an r- name that isn't a valid run id, as before (3.063895ms)
✔ the readers write nothing (5.728488ms)
✔ no pointer is null (2.85157ms)
✔ the pointer release.sh writes reads back (2.167674ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (28.992167ms)
✔ a pointer that is a directory refuses with exit 4 (1.210931ms)
✔ a state file linked out of the data root refuses (2.779979ms)
✔ a state directory linked out of the data root refuses (1.069934ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.943535ms)
✔ a missing log is empty (0.677356ms)
✔ the log reads oldest first and counts malformed lines (2.389984ms)
✔ last must be a positive integer (4.101041ms)
✔ the state readers write nothing (1.756571ms)
✔ list prints each run in the established format (213.459424ms)
✔ show prints the run in the established format (146.704832ms)
✔ show of a missing run and an invalid id exit 4 as before (380.104067ms)
✔ list and show refuse a runs directory linked out of the data root (310.571356ms)
✔ show refuses a run linked out of the data root; list reports it unknown (175.66159ms)
✖ list shows an r- name that isn't a valid run id, as before (72.884478ms)
✔ show refuses an invalid id before reading the config (55.520564ms)
ℹ tests 41
ℹ suites 0
ℹ pass 39
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1453.463752
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:220:1
✖ listRunRecords lists an r- name that isn't a valid run id, as before (3.063895ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
+ []
- [
- 'r-.bad'
- ]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/runs.test.mjs:224:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [],
expected: [ 'r-.bad' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
test at packages/runs/tests/task-cli.test.mjs:96:1
✖ list shows an r- name that isn't a valid run id, as before (72.884478ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ ''
- 'r-.bad succeeded task=t-one ws=- session=-\n'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/task-cli.test.mjs:101:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: '',
expected: 'r-.bad succeeded task=t-one ws=- session=-\n',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/state.mjs b/packages/runs/src/state.mjs
index af5cc927..6b777627 100644
--- a/packages/runs/src/state.mjs
+++ b/packages/runs/src/state.mjs
@@ -39,7 +39,7 @@ export function readActivePointer(dataRoot) {
for (const key of Object.keys(pointer)) {
if (!POINTER_KEYS.includes(key)) throw invalid(`has an unsupported key: "${key}"`);
}
- if (pointer.pointerVersion !== 1) throw invalid(`has unsupported pointerVersion ${JSON.stringify(pointer.pointerVersion)}`);
+ if (false) throw invalid(`has unsupported pointerVersion ${JSON.stringify(pointer.pointerVersion)}`);
for (const key of ["release", "imageTag", "activatedAt"]) {
if (!isNonEmptyString(pointer[key])) throw invalid(`needs a non-empty string "${key}"`);
}
@@ -0,0 +1,68 @@
✔ isRunId accepts the run id shape and nothing else (1.070238ms)
✔ a missing data root or runs directory lists nothing (15.238793ms)
✔ runs as a regular file lists nothing, as before (0.630315ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.749971ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (1.868706ms)
✔ readRunRecord returns documents and artifacts in directory order (0.975396ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.713914ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.676303ms)
✔ readRunDocument reads only the three run documents (0.521547ms)
✔ a link inside the data root is followed (0.939391ms)
✔ a data root that is itself a link is trusted as configured (0.658101ms)
✔ a run directory linked out of the data root is never read (0.61921ms)
✔ a document linked out of the data root reads as null (0.876784ms)
✔ a runs directory linked out of the data root refuses (0.544007ms)
✔ a relative link that climbs out of the data root refuses (0.387288ms)
✔ a sibling whose name starts with the data root's name is outside it (0.479327ms)
✔ a link loop refuses instead of reading as missing (0.619689ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.410005ms)
✔ an unreadable run directory refuses instead of reading as missing (0.46599ms)
✔ a link to the data root's parent is outside it (0.377274ms)
✔ listRunIds sorts whatever order the directory returns (0.719835ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.463838ms)
✔ the readers write nothing (12.891696ms)
✔ no pointer is null (13.84385ms)
✔ the pointer release.sh writes reads back (1.126812ms)
✖ a pointer that isn't the version 1 shape refuses with exit 2 (78.389391ms)
✔ a pointer that is a directory refuses with exit 4 (1.045036ms)
✔ a state file linked out of the data root refuses (2.729789ms)
✔ a state directory linked out of the data root refuses (0.95196ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.776137ms)
✔ a missing log is empty (0.563476ms)
✔ the log reads oldest first and counts malformed lines (6.585745ms)
✔ last must be a positive integer (0.943606ms)
✔ the state readers write nothing (0.995233ms)
✔ list prints each run in the established format (95.360894ms)
✔ show prints the run in the established format (99.253996ms)
✔ show of a missing run and an invalid id exit 4 as before (122.191371ms)
✔ list and show refuse a runs directory linked out of the data root (188.490155ms)
✔ show refuses a run linked out of the data root; list reports it unknown (171.206385ms)
✔ list shows an r- name that isn't a valid run id, as before (96.606326ms)
✔ show refuses an invalid id before reading the config (48.391711ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 886.752441
✖ failing tests:
test at packages/runs/tests/state.test.mjs:32:1
✖ a pointer that isn't the version 1 shape refuses with exit 2 (78.389391ms)
AssertionError [ERR_ASSERTION]: Missing expected exception: /unsupported pointerVersion 2/
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/state.test.mjs:47:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/state.mjs b/packages/runs/src/state.mjs
index af5cc927..2029992d 100644
--- a/packages/runs/src/state.mjs
+++ b/packages/runs/src/state.mjs
@@ -55,7 +55,7 @@ function logEntry(line) {
}
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return null;
if (Object.keys(entry).some((key) => !LOG_KEYS.includes(key))) return null;
- if (!["at", "event", "release", "imageTag"].every((key) => typeof entry[key] === "string")) return null;
+ if (!["at", "event", "release"].every((key) => typeof entry[key] === "string")) return null;
if (entry.note !== undefined && typeof entry.note !== "string") return null;
return entry;
}
@@ -0,0 +1,95 @@
✔ isRunId accepts the run id shape and nothing else (1.252447ms)
✔ a missing data root or runs directory lists nothing (6.093709ms)
✔ runs as a regular file lists nothing, as before (1.075597ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.214686ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (3.148367ms)
✔ readRunRecord returns documents and artifacts in directory order (1.124565ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.746849ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.009428ms)
✔ readRunDocument reads only the three run documents (0.573239ms)
✔ a link inside the data root is followed (1.02649ms)
✔ a data root that is itself a link is trusted as configured (0.812922ms)
✔ a run directory linked out of the data root is never read (1.139725ms)
✔ a document linked out of the data root reads as null (1.475949ms)
✔ a runs directory linked out of the data root refuses (0.79783ms)
✔ a relative link that climbs out of the data root refuses (0.644081ms)
✔ a sibling whose name starts with the data root's name is outside it (0.543714ms)
✔ a link loop refuses instead of reading as missing (0.716483ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.471177ms)
✔ an unreadable run directory refuses instead of reading as missing (0.534855ms)
✔ a link to the data root's parent is outside it (0.400246ms)
✔ listRunIds sorts whatever order the directory returns (0.78587ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.471629ms)
✔ the readers write nothing (2.396636ms)
✔ no pointer is null (1.913757ms)
✔ the pointer release.sh writes reads back (1.272056ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (125.829499ms)
✔ a pointer that is a directory refuses with exit 4 (1.62796ms)
✔ a state file linked out of the data root refuses (1.042007ms)
✔ a state directory linked out of the data root refuses (0.558456ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.478057ms)
✔ a missing log is empty (0.450056ms)
✖ the log reads oldest first and counts malformed lines (2.013903ms)
✔ last must be a positive integer (0.569281ms)
✔ the state readers write nothing (0.848332ms)
✔ list prints each run in the established format (82.056594ms)
✔ show prints the run in the established format (115.447797ms)
✔ show of a missing run and an invalid id exit 4 as before (143.98818ms)
✔ list and show refuse a runs directory linked out of the data root (346.242982ms)
✔ show refuses a run linked out of the data root; list reports it unknown (230.004362ms)
✔ list shows an r- name that isn't a valid run id, as before (128.157444ms)
✔ show refuses an invalid id before reading the config (43.502473ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1149.126501
✖ failing tests:
test at packages/runs/tests/state.test.mjs:87:1
✖ the log reads oldest first and counts malformed lines (2.013903ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
{
entries: [
{
at: '2026-09-03T20:57:00Z',
event: 'package',
...
{
+ at: '2026-09-03T20:57:47Z',
+ event: 'activate',
+ release: '0.0.12'
+ },
+ {
at: '2026-09-03T20:57:50Z',
event: 'refused',
imageTag: 'mosaic-poc-agent:0.84.4-r0.0.11',
note: 'health check failed (fault-injected)',
release: '0.0.11'
...
],
+ malformed: 6
- malformed: 7
}
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/state.test.mjs:110:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: { entries: [ [Object], [Object], [Object], [Object], [Object] ], malformed: 6 },
expected: { entries: [ [Object], [Object], [Object], [Object] ], malformed: 7 },
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/state.mjs b/packages/runs/src/state.mjs
index af5cc927..0b58f712 100644
--- a/packages/runs/src/state.mjs
+++ b/packages/runs/src/state.mjs
@@ -66,7 +66,7 @@ function logEntry(line) {
// rollback, which skips only lines that aren't JSON. A missing log is empty.
// With last, only the newest last well-formed entries are returned.
export function readActivationLog(dataRoot, { last } = {}) {
- if (last !== undefined && (!Number.isInteger(last) || last < 1)) {
+ if (last !== undefined && (!Number.isInteger(last) || last < 0)) {
throw new RunsError(`last must be a positive integer (got ${JSON.stringify(last)})`);
}
const state = readStateFile(dataRoot, ACTIVATION_LOG_FILE);
@@ -0,0 +1,69 @@
✔ isRunId accepts the run id shape and nothing else (1.895901ms)
✔ a missing data root or runs directory lists nothing (19.117981ms)
✔ runs as a regular file lists nothing, as before (1.156194ms)
✔ listRunIds keeps r- names only, sorted oldest first (2.786988ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (6.490428ms)
✔ readRunRecord returns documents and artifacts in directory order (3.065151ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.740552ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (2.297296ms)
✔ readRunDocument reads only the three run documents (1.292545ms)
✔ a link inside the data root is followed (2.752987ms)
✔ a data root that is itself a link is trusted as configured (1.709382ms)
✔ a run directory linked out of the data root is never read (7.227673ms)
✔ a document linked out of the data root reads as null (2.243894ms)
✔ a runs directory linked out of the data root refuses (1.063142ms)
✔ a relative link that climbs out of the data root refuses (0.744008ms)
✔ a sibling whose name starts with the data root's name is outside it (0.882094ms)
✔ a link loop refuses instead of reading as missing (1.142281ms)
✔ an unreadable runs directory refuses instead of listing nothing (3.150174ms)
✔ an unreadable run directory refuses instead of reading as missing (0.853351ms)
✔ a link to the data root's parent is outside it (0.651315ms)
✔ listRunIds sorts whatever order the directory returns (1.091854ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.60074ms)
✔ the readers write nothing (3.036123ms)
✔ no pointer is null (14.514676ms)
✔ the pointer release.sh writes reads back (3.228549ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (72.542192ms)
✔ a pointer that is a directory refuses with exit 4 (1.930314ms)
✔ a state file linked out of the data root refuses (1.407715ms)
✔ a state directory linked out of the data root refuses (0.738704ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.801635ms)
✔ a missing log is empty (0.664066ms)
✔ the log reads oldest first and counts malformed lines (2.17924ms)
✖ last must be a positive integer (1.668759ms)
✔ the state readers write nothing (2.415105ms)
✔ list prints each run in the established format (147.726988ms)
✔ show prints the run in the established format (108.731864ms)
✔ show of a missing run and an invalid id exit 4 as before (172.540569ms)
✔ list and show refuse a runs directory linked out of the data root (269.573796ms)
✔ show refuses a run linked out of the data root; list reports it unknown (185.736528ms)
✔ list shows an r- name that isn't a valid run id, as before (100.838017ms)
✔ show refuses an invalid id before reading the config (64.187937ms)
ℹ tests 41
ℹ suites 0
ℹ pass 40
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1135.081876
✖ failing tests:
test at packages/runs/tests/state.test.mjs:115:1
✖ last must be a positive integer (1.668759ms)
AssertionError [ERR_ASSERTION]: Missing expected exception: 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56b/mutwt/packages/runs/tests/state.test.mjs:118:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /last must be a positive integer/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,15 @@
agents/rocko/work/queue-56/byte-check.sh: OK
agents/rocko/work/queue-56/delta-check.sh: OK
agents/rocko/work/queue-56/seed.sh: OK
packages/runs/package.json: OK
packages/runs/README.md: OK
packages/runs/src/errors.mjs: OK
packages/runs/src/index.mjs: OK
packages/runs/src/paths.mjs: OK
packages/runs/src/runs.mjs: OK
packages/runs/src/state.mjs: OK
packages/runs/tests/helpers.mjs: OK
packages/runs/tests/runs.test.mjs: OK
packages/runs/tests/state.test.mjs: OK
packages/runs/tests/task-cli.test.mjs: OK
scripts/mosaic-task.mjs: OK
@@ -0,0 +1,32 @@
# mutate.py <id>: apply one named mutant to the candidate (exact text, must match once). Run from the tree root.
import sys
P, R, T, X = "packages/runs/src/paths.mjs", "packages/runs/src/runs.mjs", "packages/runs/src/state.mjs", "scripts/mosaic-task.mjs"
M = {
"D01": [(X, " if (!isRunId(runId)) {\n fail(4, `invalid run id", " if (false) {\n fail(4, `invalid run id")],
"D03": [(P, " } catch {\n return null;\n }\n}", " } catch (error) {\n if (error instanceof RunsError) throw error;\n return null;\n }\n}")],
"D04": [(R, " if (resolveInside(dataRoot, RUNS_DIRNAME) === null) return null;\n", "")],
"D05": [(T, "entries.slice(-last)", "entries.slice(0, last)")],
"D06": [(T, ' if (entry.note !== undefined && typeof entry.note !== "string") return null;\n', "")],
"D07": [(R, " if (isMissing(error)) return [];\n throw new RunsError(`cannot read ${root}", " return [];\n throw new RunsError(`cannot read ${root}")],
"D08": [(T, " } catch (error) {\n throw new RunsError(`cannot read ${file}", " } catch (error) {\n return null;\n throw new RunsError(`cannot read ${file}")],
"D09": [(T, "if (!POINTER_KEYS.includes(key))", "if (false)")],
"D10": [(P, 'return relative === "" ||', "return false ||")],
"D11": [(X, "if (error instanceof RunsError) fail(error.exitCode, error.message);\n throw error;\n }\n}\n\nfunction listRuns", "if (false) fail(error.exitCode, error.message);\n throw error;\n }\n}\n\nfunction listRuns")],
"D13": [(R, " if (isMissing(error)) return null;\n throw new RunsError(`cannot read ${dir}", " throw new RunsError(`cannot read ${dir}")],
"D14": [(P, "real = fs.realpathSync(target);", "real = path.resolve(target);")],
"D15": [(P, "root = fs.realpathSync(dataRoot);", "root = path.resolve(dataRoot);")],
"D16": [(R, "{0,63}$/", "{0,64}$/")],
"D17": [(R, ' result: readJsonObject(dataRoot, RUNS_DIRNAME, runId, "result.json"),', ' result: readRunDocument(dataRoot, runId, "result.json"),')],
"D18": [(T, 'if (line.trim() === "") continue;', 'if (line === "") continue;')],
"D19": [(R, " requireRunId(runId);\n if (!RUN_DOCUMENTS", " if (!RUN_DOCUMENTS")],
"D20": [(R, 'filter((name) => name.startsWith("r-")).sort()', "filter(isRunId).sort()")],
"D21": [(T, "if (pointer.pointerVersion !== 1)", "if (false)")],
"D22": [(T, '["at", "event", "release", "imageTag"].every', '["at", "event", "release"].every')],
"D23": [(T, "last < 1)", "last < 0)")],
}
for f, old, new in M[sys.argv[1]]:
s = open(f).read()
n = s.count(old)
if n != 1: sys.exit(f"{sys.argv[1]}: {n} matches in {f}")
open(f, "w").write(s.replace(old, new))
print(sys.argv[1], "applied")
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# Each mutant against the packages/runs suite in a second candidate worktree, then restores from the index.
export TMPDIR=~/darkwing-scratch/r56b/tmp
S=~/darkwing-scratch/r56b; M=$S/mut; W=$S/mutwt
: > $M/summary.txt
echo "start $(date -u +%FT%TZ)" >> $M/summary.txt
for v in $(python3 -c 'import re;print(" ".join(sorted(set(re.findall(r"\"(D\d\d)\"", open("'$M'/mutate.py").read())))))'); do
(cd $W && git checkout -q -- packages scripts && python3 $M/mutate.py $v > /dev/null && git diff > $M/$v.diff) || { echo "$v APPLY-FAILED" >> $M/summary.txt; continue; }
(cd $W && env -u NODE_TEST_CONTEXT node --test packages/runs/tests/*.test.mjs > $M/$v.txt 2>&1); e=$?
r=killed; [ $e = 0 ] && r=SURVIVED
echo "$v $r exit=$e $(grep -E '^ℹ (pass|fail)' $M/$v.txt | tr '\n' ' ')" >> $M/summary.txt
done
(cd $W && git checkout -q -- packages scripts && sha256sum -c $S/pkt/candidate-manifest.sha256) > $M/manifest-after.txt 2>&1
echo "end $(date -u +%FT%TZ)" >> $M/summary.txt
echo DONE >> $M/summary.txt
@@ -0,0 +1,24 @@
start 2026-10-10T17:12:08Z
D01 killed exit=1 ℹ pass 40 ℹ fail 1
D03 killed exit=1 ℹ pass 37 ℹ fail 4
D04 killed exit=1 ℹ pass 39 ℹ fail 2
D05 killed exit=1 ℹ pass 40 ℹ fail 1
D06 killed exit=1 ℹ pass 40 ℹ fail 1
D07 killed exit=1 ℹ pass 40 ℹ fail 1
D08 killed exit=1 ℹ pass 40 ℹ fail 1
D09 killed exit=1 ℹ pass 40 ℹ fail 1
D10 SURVIVED exit=0 ℹ pass 41 ℹ fail 0
D11 killed exit=1 ℹ pass 39 ℹ fail 2
D13 killed exit=1 ℹ pass 40 ℹ fail 1
D14 killed exit=1 ℹ pass 27 ℹ fail 14
D15 killed exit=1 ℹ pass 40 ℹ fail 1
D16 killed exit=1 ℹ pass 40 ℹ fail 1
D17 killed exit=1 ℹ pass 39 ℹ fail 2
D18 killed exit=1 ℹ pass 40 ℹ fail 1
D19 killed exit=1 ℹ pass 40 ℹ fail 1
D20 killed exit=1 ℹ pass 39 ℹ fail 2
D21 killed exit=1 ℹ pass 40 ℹ fail 1
D22 killed exit=1 ℹ pass 40 ℹ fail 1
D23 killed exit=1 ℹ pass 40 ℹ fail 1
end 2026-10-10T17:12:34Z
DONE
@@ -0,0 +1,2 @@
byte-check: 16 cases, stdout, stderr and exit identical
byte-check: data root unchanged
@@ -0,0 +1,118 @@
== run directory linked out of the data root: list
base: exit 0
out| r-20260101T000000Z-aaaaaa succeeded task=t-out ws=- session=-
cand: exit 0
out| r-20260101T000000Z-aaaaaa unknown task=- ws=- session=-
== run directory linked out of the data root: show
base: exit 0
out| run: r-20260101T000000Z-aaaaaa
out| status: succeeded
out| task: t-out
out| adapter: (see config) provider=p model=m
out| request: "r"
out| response: "s"
out| timing: a -> b (1 ms)
out| exit: 0
out| artifacts: result.json
cand: exit 4
err| mosaic-task: /home/jwoltje/darkwing-scratch/r56b/dc/run-link/data/runs/r-20260101T000000Z-aaaaaa resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/dc/run-link/data)
== result.json linked out of the data root: list
base: exit 0
out| r-20260101T000000Z-aaaaaa succeeded task=t-out ws=- session=-
cand: exit 0
out| r-20260101T000000Z-aaaaaa unknown task=- ws=- session=-
== result.json linked out of the data root: show
base: exit 0
out| run: r-20260101T000000Z-aaaaaa
out| status: succeeded
out| task: t-out
out| adapter: (see config) provider=p model=m
out| request: "r"
out| response: "s"
out| timing: a -> b (1 ms)
out| exit: 0
out| artifacts: result.json
cand: exit 0
out| run: r-20260101T000000Z-aaaaaa
out| result.json: (missing or unreadable)
out| artifacts: result.json
== runs directory linked out of the data root: list
base: exit 0
out| r-20260101T000000Z-aaaaaa succeeded task=t-out ws=- session=-
cand: exit 4
err| mosaic-task: /home/jwoltje/darkwing-scratch/r56b/dc/runs-link/data/runs resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/dc/runs-link/data)
== runs directory linked out of the data root: show
base: exit 0
out| run: r-20260101T000000Z-aaaaaa
out| status: succeeded
out| task: t-out
out| adapter: (see config) provider=p model=m
out| request: "r"
out| response: "s"
out| timing: a -> b (1 ms)
out| exit: 0
out| artifacts: result.json
cand: exit 4
err| mosaic-task: /home/jwoltje/darkwing-scratch/r56b/dc/runs-link/data/runs resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/dc/runs-link/data)
== result.json is 5 and task.json is []: list
base: exit 1
err| file:///home/jwoltje/darkwing-scratch/r56b/base/scripts/mosaic-task.mjs:483
cand: exit 0
out| r-20260101T000000Z-aaaaaa unknown task=- ws=- session=-
== result.json is 5 and task.json is []: show
base: exit 0
out| run: r-20260101T000000Z-aaaaaa
out| status: undefined
out| task: undefined
out| adapter: (see config) provider=undefined model=undefined
out| request: undefined
out| response: undefined
out| timing: undefined -> undefined (undefined ms)
out| exit: undefined
out| task snapshot: task.json present
out| artifacts: result.json, task.json
cand: exit 0
out| run: r-20260101T000000Z-aaaaaa
out| result.json: (missing or unreadable)
out| artifacts: result.json, task.json
== run is a regular file: show
base: exit 1
out| run: r-20260101T000000Z-aaaaaa
out| result.json: (missing or unreadable)
err| node:fs:1954
cand: exit 4
err| mosaic-task: run not found: r-20260101T000000Z-aaaaaa (under /home/jwoltje/darkwing-scratch/r56b/dc/run-file/data/runs)
== run is a link loop: show
base: exit 4
err| mosaic-task: run not found: r-20260101T000000Z-aaaaaa (under /home/jwoltje/darkwing-scratch/r56b/dc/loop/data/runs)
cand: exit 4
err| mosaic-task: cannot resolve /home/jwoltje/darkwing-scratch/r56b/dc/loop/data/runs/r-20260101T000000Z-aaaaaa: ELOOP
== runs directory unreadable: list
base: exit 0
cand: exit 4
err| mosaic-task: cannot read /home/jwoltje/darkwing-scratch/r56b/dc/unreadable/data/runs: EACCES
== runs directory unreadable: show
base: exit 4
err| mosaic-task: run not found: r-20260101T000000Z-aaaaaa (under /home/jwoltje/darkwing-scratch/r56b/dc/unreadable/data/runs)
cand: exit 4
err| mosaic-task: cannot resolve /home/jwoltje/darkwing-scratch/r56b/dc/unreadable/data/runs/r-20260101T000000Z-aaaaaa: EACCES
== run directory unreadable: show
base: exit 1
out| run: r-20260101T000000Z-aaaaaa
out| result.json: (missing or unreadable)
err| node:fs:1954
cand: exit 4
err| mosaic-task: cannot read /home/jwoltje/darkwing-scratch/r56b/dc/run-unreadable/data/runs/r-20260101T000000Z-aaaaaa: EACCES
@@ -0,0 +1,49 @@
✔ isRunId accepts the run id shape and nothing else (0.845764ms)
✔ a missing data root or runs directory lists nothing (1.27599ms)
✔ runs as a regular file lists nothing, as before (0.481538ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.620424ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (1.641509ms)
✔ readRunRecord returns documents and artifacts in directory order (0.822114ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.561666ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.531563ms)
✔ readRunDocument reads only the three run documents (0.427644ms)
✔ a link inside the data root is followed (0.795518ms)
✔ a data root that is itself a link is trusted as configured (0.549674ms)
✔ a run directory linked out of the data root is never read (0.551772ms)
✔ a document linked out of the data root reads as null (0.799823ms)
✔ a runs directory linked out of the data root refuses (0.528601ms)
✔ a relative link that climbs out of the data root refuses (0.359757ms)
✔ a sibling whose name starts with the data root's name is outside it (0.428236ms)
✔ a link loop refuses instead of reading as missing (0.558513ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.397344ms)
✔ an unreadable run directory refuses instead of reading as missing (0.438001ms)
✔ a link to the data root's parent is outside it (0.427799ms)
✔ listRunIds sorts whatever order the directory returns (0.733867ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.434006ms)
✔ the readers write nothing (1.814734ms)
✔ no pointer is null (1.605828ms)
✔ the pointer release.sh writes reads back (0.95797ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (357.065646ms)
✔ a pointer that is a directory refuses with exit 4 (0.599972ms)
✔ a state file linked out of the data root refuses (0.819613ms)
✔ a state directory linked out of the data root refuses (0.470093ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.414246ms)
✔ a missing log is empty (46.073141ms)
✔ the log reads oldest first and counts malformed lines (1.021399ms)
✔ last must be a positive integer (0.452181ms)
✔ the state readers write nothing (0.734058ms)
✔ list prints each run in the established format (68.62278ms)
✔ show prints the run in the established format (65.989299ms)
✔ show of a missing run and an invalid id exit 4 as before (276.117437ms)
✔ list and show refuse a runs directory linked out of the data root (185.491853ms)
✔ show refuses a run linked out of the data root; list reports it unknown (215.611415ms)
✔ list shows an r- name that isn't a valid run id, as before (100.451058ms)
✔ show refuses an invalid id before reading the config (51.977553ms)
ℹ tests 41
ℹ suites 0
ℹ pass 41
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1017.118046
@@ -0,0 +1,27 @@
== Q1 runs/ linked out of the data root to nothing: list
base: exit 0
cand: exit 0
== Q1 same: show
base: exit 4
err| mosaic-task: run not found: r-20260101T000000Z-aaaaaa (under /home/jwoltje/darkwing-scratch/r56b/probes/q1/data/runs)
cand: exit 4
err| mosaic-task: run not found: r-20260101T000000Z-aaaaaa (under /home/jwoltje/darkwing-scratch/r56b/probes/q1/data/runs)
== Q2 runs/ linked out to an empty directory that exists: list
base: exit 0
cand: exit 4
err| mosaic-task: /home/jwoltje/darkwing-scratch/r56b/probes/q2/data/runs resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/probes/q2/data)
== Q3 state/ linked out to nothing (candidate module)
readActivePointer: null
readActivationLog: {"entries":[],"malformed":0}
== Q4 state/ linked out to an empty directory that exists (round 1 P5)
readActivePointer: null
readActivationLog: {"entries":[],"malformed":0}
== Q4b same, with active.json there
readActivePointer: RunsError 4 /home/jwoltje/darkwing-scratch/r56b/probes/q4/data/state/active.json resolves outside the data root (/home/jwoltje/darkwing-scratch/r56b/probes/q4/data)
readActivationLog: {"entries":[],"malformed":0}
@@ -0,0 +1,7 @@
packages/runs/src/errors.mjs: OK
packages/runs/src/index.mjs: OK
packages/runs/src/paths.mjs: OK
packages/runs/src/runs.mjs: OK
packages/runs/src/state.mjs: FAILED
scripts/mosaic-task.mjs: OK
sha256sum: WARNING: 1 computed checksum did NOT match
@@ -0,0 +1,12 @@
diff -r /home/jwoltje/darkwing-scratch/r56b/r1tree/packages/runs/src/state.mjs /home/jwoltje/darkwing-scratch/r56b/cand/packages/runs/src/state.mjs
63,66c63,67
< // The activation log as { entries, malformed }, oldest first. Lines that
< // aren't a well-formed entry are counted in malformed and skipped, the way
< // release.sh rollback skips them. A missing log is empty. With last, only
< // the newest last well-formed entries are returned.
---
> // The activation log as { entries, malformed }, oldest first. A line that
> // isn't JSON, or is JSON but not the entry shape release.sh writes, is
> // counted in malformed and skipped. This is stricter than release.sh
> // rollback, which skips only lines that aren't JSON. A missing log is empty.
> // With last, only the newest last well-formed entries are returned.
@@ -0,0 +1,13 @@
start 2026-10-10T17:12:07Z
node-runs exit=0 ℹ pass 41 ℹ fail 0
test-auth exit=0 selftest: 15 passed, 0 failed
test-conductor exit=0 selftest: 17 passed, 0 failed
test-config exit=0 selftest: 24 passed, 0 failed
test-discord exit=1 discord suite: 65 passed, 1 failed
test-extension-package exit=0 extension package selftest: 18 passed, 0 failed
test-foundation exit=0 selftest: 44 passed, 0 failed
test-queue exit=0 queue suite: 27 passed, 0 failed
test-release exit=0 selftest: 4 passed, 0 failed
test-task exit=0 selftest: 26 passed, 0 failed
end 2026-10-10T17:14:22Z
DONE
@@ -0,0 +1,17 @@
OK status with missing harness credential exits 3 and still lists accounts
OK status reports harness credential (read-only) + mosaic accounts
OK api key material never reaches output
OK oauth token material never reaches output
OK unparseable credential file exits 2
OK symlinked credential file exits 4
OK env-side credential names reported
OK env var values never reach output
OK accounts without an accounts dir reports none and creates nothing
OK accounts lists files and marks the active one
OK loose account perms flagged in listing
OK agent --auth with missing account file refuses (exit 4)
OK agent --auth with non-0600 account file refuses
OK agent --auth with invalid account name refuses
OK auth.sh without valid config refuses
selftest: 15 passed, 0 failed
@@ -0,0 +1,55 @@
Note: switching to '75ab164698176178f196009f14f994e0192048a7'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
Not currently on any branch.
nothing to commit, working tree clean
Note: switching to '75ab164698176178f196009f14f994e0192048a7'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
OK dry-run committed nothing
OK apply: allowed change exits 0 (exit 0)
OK apply: attribution in commit subject
OK apply: target tree clean after commit
OK disallowed path refused (exit 1)
OK disallowed path: target untouched
OK syntax gate refused broken .mjs (exit 1)
OK syntax gate: target untouched
OK suite failure refused (exit 1)
OK suite failure: target reverted to clean
OK disabled policy refused (exit 2)
OK disabled policy: target untouched
OK failed run refused (exit 1)
OK failed run: target untouched
OK missing run exits 4 (exit 4)
OK invalid policy exits 2 (exit 2)
selftest: 17 passed, 0 failed
@@ -0,0 +1,26 @@
OK absent adapter defaults to pi
OK adapter mock validates (exit 0)
OK unsupported adapter exits 2 (exit 2)
OK env exports adapter
OK bootstrap creates default when absent (exit 0)
OK bootstrap wrote config file
OK bootstrap is idempotent on existing config (exit 0)
OK bootstrap did not rewrite existing config
OK validate missing config exits 3 (exit 3)
OK malformed JSON exits 2 (exit 2)
OK unsupported configVersion exits 2 (exit 2)
OK unknown top-level key exits 2 (exit 2)
OK unknown execution key exits 2 (exit 2)
OK unsupported backend exits 2 (exit 2)
OK unsupported environment exits 2 (exit 2)
OK relative dataRoot exits 2 (exit 2)
OK non-canonical dataRoot exits 2 (exit 2)
OK filesystem root dataRoot exits 2 (exit 2)
OK home directory dataRoot exits 2 (exit 2)
OK dataRoot containing config dir exits 2 (exit 2)
OK control character in provider exits 2 (exit 2)
OK symlinked config file exits 2 (exit 2)
OK env exports resolve correctly
OK failed validation modified nothing
selftest: 24 passed, 0 failed
@@ -0,0 +1,70 @@
toolchain: node v26.8.1
OK syntax: packages/discord/src/approvals.mjs
OK syntax: packages/discord/src/authorize.mjs
OK syntax: packages/discord/src/binding.mjs
OK syntax: packages/discord/src/cli.mjs
OK syntax: packages/discord/src/connector.mjs
OK syntax: packages/discord/src/context.mjs
OK syntax: packages/discord/src/engine-pi.mjs
OK syntax: packages/discord/src/errors.mjs
OK syntax: packages/discord/src/gateway.mjs
OK syntax: packages/discord/src/git.mjs
OK syntax: packages/discord/src/journal.mjs
OK syntax: packages/discord/src/notify.mjs
OK syntax: packages/discord/src/rest.mjs
OK syntax: packages/discord/src/setspark.mjs
OK syntax: packages/discord/src/tools.mjs
OK syntax: packages/discord/src/web.mjs
OK syntax: packages/discord/bin/git-credential.mjs
OK syntax: packages/discord/extension/tools.mjs
OK syntax: packages/discord/tests/approvals.test.mjs
OK syntax: packages/discord/tests/authorize.test.mjs
OK syntax: packages/discord/tests/binding.test.mjs
OK syntax: packages/discord/tests/connector.test.mjs
OK syntax: packages/discord/tests/context.test.mjs
OK syntax: packages/discord/tests/engine.test.mjs
OK syntax: packages/discord/tests/fake-pi.mjs
OK syntax: packages/discord/tests/gateway.test.mjs
OK syntax: packages/discord/tests/git.test.mjs
OK syntax: packages/discord/tests/helpers.mjs
OK syntax: packages/discord/tests/journal.test.mjs
OK syntax: packages/discord/tests/notify.test.mjs
OK syntax: packages/discord/tests/recover.test.mjs
OK syntax: packages/discord/tests/rest.test.mjs
OK syntax: packages/discord/tests/setspark.test.mjs
OK syntax: packages/discord/tests/tools.test.mjs
OK syntax: packages/discord/tests/web.test.mjs
OK syntax: packages/discord/fixtures/claim-worker.mjs
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
OK syntax: scripts/discord.sh
OK syntax: scripts/discord-service.sh
OK packages/discord declares no dependencies
OK no bot-token-shaped string in packages/discord
OK fixture binding uses placeholder ids only
OK fixture binding validates
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
OK real pi with protocol vault adds reserve_id to the git verbs
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
OK real pi refuses a git key on a read-only root (fail closed)
OK real pi with the pilot flags (--no-tools) exposes no tool at all
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test packages/discord/tests/ (ℹ pass 178)
OK scripts/discord.sh --help exits 0
OK scripts/discord.sh check without a binding exits 4
OK scripts/discord.sh recover without a binding exits 4
OK scripts/discord.sh reload without a binding exits 4
OK scripts/discord-service.sh without a command exits 4
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
OK service install writes the rendered unit (0644) and leaves no temp file
OK service install a second time reports unchanged
OK systemd-analyze verify accepts the rendered unit
OK service uninstall removes the unit file
OK service install with an unknown flag exits 4
OK service install with USER unset finishes and names the account for lingering
discord suite: 66 passed, 0 failed
@@ -0,0 +1,74 @@
toolchain: node v26.8.1
OK syntax: packages/discord/src/approvals.mjs
OK syntax: packages/discord/src/authorize.mjs
OK syntax: packages/discord/src/binding.mjs
OK syntax: packages/discord/src/cli.mjs
OK syntax: packages/discord/src/connector.mjs
OK syntax: packages/discord/src/context.mjs
OK syntax: packages/discord/src/engine-pi.mjs
OK syntax: packages/discord/src/errors.mjs
OK syntax: packages/discord/src/gateway.mjs
OK syntax: packages/discord/src/git.mjs
OK syntax: packages/discord/src/journal.mjs
OK syntax: packages/discord/src/notify.mjs
OK syntax: packages/discord/src/rest.mjs
OK syntax: packages/discord/src/setspark.mjs
OK syntax: packages/discord/src/tools.mjs
OK syntax: packages/discord/src/web.mjs
OK syntax: packages/discord/bin/git-credential.mjs
OK syntax: packages/discord/extension/tools.mjs
OK syntax: packages/discord/tests/approvals.test.mjs
OK syntax: packages/discord/tests/authorize.test.mjs
OK syntax: packages/discord/tests/binding.test.mjs
OK syntax: packages/discord/tests/connector.test.mjs
OK syntax: packages/discord/tests/context.test.mjs
OK syntax: packages/discord/tests/engine.test.mjs
OK syntax: packages/discord/tests/fake-pi.mjs
OK syntax: packages/discord/tests/gateway.test.mjs
OK syntax: packages/discord/tests/git.test.mjs
OK syntax: packages/discord/tests/helpers.mjs
OK syntax: packages/discord/tests/journal.test.mjs
OK syntax: packages/discord/tests/notify.test.mjs
OK syntax: packages/discord/tests/recover.test.mjs
OK syntax: packages/discord/tests/rest.test.mjs
OK syntax: packages/discord/tests/setspark.test.mjs
OK syntax: packages/discord/tests/tools.test.mjs
OK syntax: packages/discord/tests/web.test.mjs
OK syntax: packages/discord/fixtures/claim-worker.mjs
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
OK syntax: scripts/discord.sh
OK syntax: scripts/discord-service.sh
OK packages/discord declares no dependencies
OK no bot-token-shaped string in packages/discord
OK fixture binding uses placeholder ids only
OK fixture binding validates
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
OK real pi with protocol vault adds reserve_id to the git verbs
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
OK real pi refuses a git key on a read-only root (fail closed)
OK real pi with the pilot flags (--no-tools) exposes no tool at all
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
FAIL node --test packages/discord/tests/ (ℹ pass 177)
✖ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (309.121655ms)
✖ failing tests:
✖ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (309.121655ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
OK scripts/discord.sh --help exits 0
OK scripts/discord.sh check without a binding exits 4
OK scripts/discord.sh recover without a binding exits 4
OK scripts/discord.sh reload without a binding exits 4
OK scripts/discord-service.sh without a command exits 4
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
OK service install writes the rendered unit (0644) and leaves no temp file
OK service install a second time reports unchanged
OK systemd-analyze verify accepts the rendered unit
OK service uninstall removes the unit file
OK service install with an unknown flag exits 4
OK service install with USER unset finishes and names the account for lingering
discord suite: 65 passed, 1 failed
@@ -0,0 +1,21 @@
OK initial ordinary-file install
OK installed tree matches canonical source
OK installed tree has no symlinks
OK check detects installation drift
OK sync refuses to overwrite installation drift
OK check detects an extra destination file
OK check detects an extra destination directory
OK check rejects a destination symlink
OK sync accepts a canonical source update
OK updated installation matches canonical source
scripts/test-extension-package.sh: line 14: 1178504 Killed "$@" > /dev/null 2>&1
OK forced interruption kills the replacing process
OK next invocation recovers old consistent installation
OK interrupted replacement rolled back
OK sync succeeds after interruption recovery
OK unlocked stale lock file does not block
OK active lock refuses a concurrent sync
OK source symlink fails closed
OK nested second entrypoint fails closed
extension package selftest: 18 passed, 0 failed
@@ -0,0 +1,53 @@
toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0
OK syntax: scripts/foundation-inspect.mjs
OK syntax: scripts/foundation/strict-json.mjs
OK syntax: scripts/foundation/canonical.mjs
OK syntax: scripts/foundation/resolve.mjs
OK syntax: scripts/foundation/validate-record.mjs
OK syntax: scripts/foundation/fixtures/build-fixtures.mjs
OK syntax: scripts/foundation/canonical.test.mjs
OK syntax: scripts/foundation/cli.test.mjs
OK syntax: scripts/foundation/fixtures.test.mjs
OK syntax: scripts/foundation/resolve.test.mjs
OK syntax: scripts/foundation/strict-json.test.mjs
OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)
OK fixture generator runs
OK checked-in fixtures/bundles equal a fresh generation
OK checked-in fixtures/raw equal a fresh generation
OK checked-in fixtures/index.json equal a fresh generation
OK checked-in demo bundles equal a fresh generation
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test scripts/foundation/ (ℹ pass 80)
OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)
platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999)
node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases)
schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
profile column (schema-valid records only): profile-valid 510, profile-invalid 30
profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns
OK oracle: zero schema-column disagreements with the pinned checker
OK oracle: strict-only profile refusals are counted and asserted
OK demo: permitted read preview exits 0 (exit 0)
OK demo: permitted file.change preview exits 0 (exit 0)
OK demo: assignment.change proposal is unresolved (exit 3) (exit 3)
OK demo: revoked registration is refused (exit 3) (exit 3)
OK demo: message is not authority (exit 3) (exit 3)
OK usage: no arguments exits 2 (exit 2)
OK io: missing file exits 4 (exit 4)
OK io: directory exits 4 (exit 4)
OK io: symlink exits 4 (O_NOFOLLOW) (exit 4)
OK bound: oversize fixture exits 2 (exit 2)
OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2)
OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2)
OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0)
OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed
OK text output starts with the disclaimer
OK json output is valid JSON with result allowed and exactly the charter §7 fields
OK json golden matches byte-for-byte
OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal
OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs
OK canary never printed (bundle run and credential-file run)
OK a non-bundle JSON file is refused at the shape gate, not read into output
OK no field of the non-bundle file is echoed
selftest: 44 passed, 0 failed
@@ -0,0 +1,35 @@
toolchain: node v26.8.1, git version 2.55.0
OK syntax: packages/queue/src/cli.mjs
OK syntax: packages/queue/src/errors.mjs
OK syntax: packages/queue/src/io.mjs
OK syntax: packages/queue/src/lock.mjs
OK syntax: packages/queue/src/queue.mjs
OK syntax: packages/queue/src/review.mjs
OK syntax: packages/queue/src/store.mjs
OK syntax: packages/queue/tests/commit.test.mjs
OK syntax: packages/queue/tests/data.test.mjs
OK syntax: packages/queue/tests/dispatch.test.mjs
OK syntax: packages/queue/tests/helpers.mjs
OK syntax: packages/queue/tests/lock.test.mjs
OK syntax: packages/queue/tests/migration.test.mjs
OK syntax: packages/queue/tests/review.test.mjs
OK syntax: packages/queue/tests/store.test.mjs
OK syntax: packages/queue/tests/write.test.mjs
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
OK syntax: scripts/queue-commit.sh
OK syntax: scripts/git-hooks/pre-commit
OK syntax: scripts/mosaic
OK queue-commit.sh, the guard and scripts/mosaic are executable
OK packages/queue declares no dependencies
ℹ tests 148
ℹ pass 148
ℹ fail 0
OK node --test packages/queue/tests/
OK scripts/mosaic queue help
skip queue verify and render --check: this checkout (/home/jwoltje/darkwing-scratch/r56b/cand) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
queue suite: 27 passed, 0 failed
@@ -0,0 +1,16 @@
OK valid RELEASE resolves (exit 0)
OK invalid RELEASE exits 1 (exit 1)
OK missing RELEASE exits 1 (exit 1)
OK valid RELEASE leaves image tag consistent with version
OK status safe on empty state (exit 0)
OK status created no pointer
OK fault-injected activation refuses (exit 1)
OK refused activation wrote no pointer
OK refusal logged exactly once with valid fields
OK healthy activation succeeds (exit 0)
OK pointer written with valid fields
OK repeat activation succeeds (log grows) (exit 0)
OK log is append-only across activations
OK rollback without previous refuses (exit 1)
selftest: 14 passed, 0 failed
@@ -0,0 +1,7 @@
OK valid RELEASE resolves (exit 0)
OK invalid RELEASE exits 1 (exit 1)
OK missing RELEASE exits 1 (exit 1)
OK valid RELEASE leaves image tag consistent with version
skip state-machine cases (docker daemon unavailable)
selftest: 4 passed, 0 failed
@@ -0,0 +1,32 @@
OK valid task validates (exit 0)
OK unknown task key exits 2 (exit 2)
OK unsupported taskVersion exits 2 (exit 2)
OK invalid task id exits 2 (exit 2)
OK empty prompt exits 2 (exit 2)
OK NUL in expectExact exits 2 (exit 2)
OK out-of-range timeout exits 2 (exit 2)
OK missing mission file exits 4 (exit 4)
OK task with valid mission validates (exit 0)
OK invalid mission exits 2 (exit 2)
OK validate missing task exits 4 (exit 4)
OK validation does not modify the task file
OK prune dry-run exits 0 (exit 0)
OK dry-run deleted nothing
OK prune --keep=2 --yes removes oldest (exit 0)
OK kept exactly 2 newest runs
OK newest run kept, oldest pruned
OK append-only receipt written (3 entries)
OK sessions/workspaces untouched by prune
OK prune with invalid keep exits 4 (exit 4)
skip adapter seam cases (docker daemon unavailable)
skip workspace/capability cases (docker daemon unavailable)
skip live task cases (docker unavailable)
OK onboard without name exits 4 (non-interactive) (exit 4)
OK onboard --name renders profile (exit 0)
OK profile written
OK canon structure: required filled, optional placeholdered
OK canon sections present
skip live recall pair: user recall run, recalled user name (docker daemon unavailable)
OK no agent identity on headless run
selftest: 26 passed, 0 failed
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
# Row 56 round 2 probes: the README's link wording for runs/ and state/, base against candidate.
export TMPDIR=~/darkwing-scratch/r56b/tmp
S=~/darkwing-scratch/r56b; W=$S/probes; rm -rf $W; mkdir -p $W
A=r-20260101T000000Z-aaaaaa
root() { mkdir -p "$W/$1/data" "$W/$1/outside"; printf '{"configVersion":1,"environment":"development","dataRoot":"%s","execution":{"backend":"docker","provider":"zai","model":"m"}}\n' "$W/$1/data" > "$W/$1/config.json"; echo "$W/$1/data"; }
run_case() { local name="$1" cfg="$2"; shift 2
for tree in base cand; do
local rc=0 out
out="$(cd $S/$tree && env -u NODE_TEST_CONTEXT MOSAIC_CONFIG="$cfg" node scripts/mosaic-task.mjs "$@" 2>"$W/$name.$tree.err")" || rc=$?
printf ' %s: exit %s\n' "$tree" "$rc"; [ -z "$out" ] || printf '%s\n' "$out" | sed 's/^/ out| /'
[ -s "$W/$name.$tree.err" ] && head -1 "$W/$name.$tree.err" | sed 's/^/ err| /'
done; }
state_case() {
(cd $S/cand && node -e '
import("./packages/runs/src/index.mjs").then((m) => {
for (const f of ["readActivePointer", "readActivationLog"]) {
try { console.log(" " + f + ": " + JSON.stringify(m[f](process.argv[1]))); } catch (e) { console.log(" " + f + ": " + e.name + " " + e.exitCode + " " + e.message); }
}
});' "$1"); }
hdr() { printf '\n== %s\n' "$*"; }
D="$(root q1)"; ln -s "$W/q1/outside/absent" "$D/runs"
hdr "Q1 runs/ linked out of the data root to nothing: list"; run_case q1l "$W/q1/config.json" list
hdr "Q1 same: show"; run_case q1s "$W/q1/config.json" show "$A"
D="$(root q2)"; ln -s "$W/q2/outside" "$D/runs"
hdr "Q2 runs/ linked out to an empty directory that exists: list"; run_case q2 "$W/q2/config.json" list
D="$(root q3)"; ln -s "$W/q3/outside/absent" "$D/state"
hdr "Q3 state/ linked out to nothing (candidate module)"; state_case "$D"
D="$(root q4)"; ln -s "$W/q4/outside" "$D/state"
hdr "Q4 state/ linked out to an empty directory that exists (round 1 P5)"; state_case "$D"
printf '{"pointerVersion":1,"release":"x","imageTag":"y","activatedAt":"z"}\n' > "$W/q4/outside/active.json"
hdr "Q4b same, with active.json there"; state_case "$D"
@@ -0,0 +1,154 @@
# Row 56, runs and releases reader, round 2 review (Darkwing)
Issue #1545, request comment 27120, queue revs 350-354. Base `75ab1646`.
Candidate manifest sha256
`2727198f7e50b935205546a60ea97d8d4f20d87cad2a88e10477148c8d3dc42e`,
`build.patch` sha256 `d2fa5fb7…`. Both match comment 27120, and the
packet manifest checks clean. This is the delta review Sage asked for,
against round 1 (`ed3c5392`, my approval in comment 27115).
Verdict: **approve**. `src/` and `scripts/mosaic-task.mjs` are the round 1
bytes apart from one comment in `state.mjs`. D01 (Rocko's M31), D17, D20
and D22 now die on assertions in the new tests. Every suite is green. The
README fixes for my notes 4b and 4c are right. The fix for 4a is half
right: it covers a `state/` link to nothing, and the sentence above it
still says a `state/` link out refuses, which isn't what happens when the
link points at a directory that exists. That is wording, nothing outside
is read, and I don't think it needs a round 3.
## Method
- Detached worktrees at `75ab1646`: `base`, `cand` and `mutwt`, the last
two with `git apply --index build.patch`, then `sha256sum -c` on the
manifest, 15 OK each. `mutwt` still checks 15 OK after the mutant runs
(`r2/mut/manifest-after.txt`).
- A fourth worktree at `dc96f87b` with round 1's `build.patch` applied
(15 OK against `ed3c5392`). `diff -r` of `packages/runs/src` and `cmp`
of `scripts/mosaic-task.mjs` against the round 2 candidate
(`r2/out/src-diff.txt`, `r2/out/src-against-r1.txt`).
- `diff -u` of the three test files, the README and `delta-check.sh`
against round 1. The result matches `out/round2-delta.diff`.
- `r2/gate.sh`: the packages/runs suite, then every `scripts/test-*.sh`
with `DOCKER_HOST=unix:///nonexistent.sock`, 17:12:07Z to 17:14:22Z.
- Round 1's 21 mutants, unchanged (`r2/mut/mutate.py`, `r2/mut/run.sh`),
17:12:08Z to 17:12:34Z in `mutwt`, alongside the gate.
- Rocko's `byte-check.sh` and `delta-check.sh` from the candidate, base
against candidate, work directories in my scratch.
- Four probes of the README's new link wording (`r2/probes.sh`, output in
`r2/out/probes.txt`).
- `test-release` with Docker, 17:14:45Z to 17:15:08Z.
Node v26.8.1, `TMPDIR=~/darkwing-scratch/r56b/tmp`.
## src/ against round 1
Five of the six files are byte-identical. `state.mjs` differs only in the
comment above `readActivationLog`:
```
// The activation log as { entries, malformed }, oldest first. A line that
// isn't JSON, or is JSON but not the entry shape release.sh writes, is
// counted in malformed and skipped. This is stricter than release.sh
// rollback, which skips only lines that aren't JSON. A missing log is empty.
// With last, only the newest last well-formed entries are returned.
```
That matches `release.sh` lines 91-99: rollback drops empty lines, skips
lines `JSON.parse` rejects, and uses the newest `activate` or `rollback`
entry whose `imageTag` is truthy and differs from the current one.
## Suites
| Suite | Result |
|---|---|
| packages/runs (node) | 41/0 |
| test-auth | 15/0 |
| test-conductor | 17/0 |
| test-config | 24/0 |
| test-discord | 65/1 in the gate, 66/0 alone (below) |
| test-extension-package | 18/0 |
| test-foundation | 44/0 |
| test-queue | 27/0 |
| test-release | 4/0 without Docker, 14/0 with it |
| test-task, Docker unreachable | 26/0 |
| byte-check.sh | 16 cases identical, data root unchanged |
| delta-check.sh | 13 cases, same as Rocko's apart from scratch paths |
The test-discord failure in the gate was "engine: when pi has not started
a timed-out turn by the end of the abort grace, the engine stops pi and
fails held prompts", a timing test that ran while the mutants and checks
were loading the machine. The candidate changes nothing under
`packages/discord`. A rerun on its own at 17:14:31Z gave 66/0
(`r2/out/test-discord-r2.txt`).
I didn't run test-task with real Docker, because it makes live model
calls. Round 2 doesn't touch `mosaic-task.mjs`, and Rocko's run gave 98/0.
## Mutants
20 of 21 killed (`r2/mut/summary.txt`). Only D10 survives, which I said in
round 1 needs no test.
| Mutant | Round 1 | Round 2, killed by |
|---|---|---|
| D01 (M31) | survived | "show refuses an invalid id before reading the config": exit 3, `configuration problem` |
| D17 | survived | "listRunRecords lists an r- name that isn't a valid run id, as before" and the CLI `list` test: exit 4, `invalid run id: "r-.bad"` |
| D20 | survived | the same two tests: the list comes back empty |
| D22 | survived | "the log reads oldest first and counts malformed lines": the entry with no `imageTag` shows up in `entries` |
Each is an `AssertionError` from the new assertions, not a load or
reference error.
## Wording
| Note | Fixed? |
|---|---|
| 4b, the "like rollback" comparison (Filbert's N1) | yes, in the README and the `state.mjs` comment, and both now match `release.sh` |
| 4c, no delta row for an unreadable run directory | yes: a README row and a `delta-check.sh` case. Base prints two lines and crashes, exit 1; the candidate refuses with EACCES, exit 4 |
| Filbert's N2, `release` as a string | yes: `{ ...good[1], release: 5 }` is now in the malformed lines |
| 4a, `state/` linked out | half: see below |
The README still says "`runs/` or `state/` resolving outside refuses with a
`RunsError`". The new paragraph after the list says a `state/` link out
"to a path that doesn't exist" reads as no release and an empty log, and
the new test covers that case. My round 1 probe P5 linked `state/` to a
directory that exists, and that case still contradicts the bullet:
| Probe | Candidate |
|---|---|
| Q1 `runs/` linked out to nothing | `list` prints nothing, exit 0; `show` says `run not found`, exit 4. Base does the same |
| Q2 `runs/` linked out to an empty directory that exists | `list` refuses, exit 4 (base: nothing, exit 0) |
| Q3 `state/` linked out to nothing | pointer `null`, log empty |
| Q4 `state/` linked out to an empty directory that exists | pointer `null`, log empty, no refusal |
| Q4b the same, with `active.json` there | `readActivePointer` refuses, exit 4 |
The module never resolves `state/` itself, only `state/active.json` and
`state/activation-log.jsonl`. So a `state/` link out refuses only once one
of those files exists behind it. A sentence that says so would fix it,
for example: "`runs/` resolving outside refuses. `state/` isn't resolved
on its own; `active.json` or `activation-log.jsonl` resolving outside
refuses, and while neither exists a `state/` link out reads as no release
and an empty log." The invariant holds either way, because nothing outside
the data root is read.
## Notes (not blocking)
1. The 4a wording above. It can ride with the `padEnd` follow-up or any
later change to this README. I don't want a round 3 for it.
2. BUILD.md describes rollback as acting on "any parsed `activate` or
`rollback` entry with a truthy `imageTag`". It also skips the entry
whose `imageTag` equals the current one. The README and the comment
don't make that claim, so only the packet is affected.
3. Correction to my round 1 review: it said "17 of 22 killed". `mutate.py`
defines 21 mutants (there is no D02 or D12), and 16 of them were
killed in round 1. The five survivors I listed were right.
## Files
- `r2/candidate-manifest.sha256`: copy of Rocko's.
- `r2/gate.sh`, `r2/out/`: suite runs, `summary.txt`, the test-discord
rerun, byte and delta checks, test-release with Docker, the `src/`
comparison with round 1, and the probe output.
- `r2/mut/`: round 1's mutant definitions and runner, diffs, outputs,
`summary.txt` and the manifest check after the runs.
- `r2/probes.sh`: Q1 to Q4b.