review(56): Darkwing round 1 packet, approve (#1545 comment 27115)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-10 12:00:44 -05:00
co-authored by Claude Opus 5.5
parent 12e18a6063
commit 6417a3e7a6
65 changed files with 2831 additions and 0 deletions
@@ -0,0 +1,15 @@
1afe07062349b097b955f0391bffb839b083492d02644421def6c39cc19ff4a4 agents/rocko/work/queue-56/byte-check.sh
96416ea2eb84490b79e98b9aa0f95673e76070f15ba2876f83dbf1bdaa919f97 agents/rocko/work/queue-56/delta-check.sh
15d575cc45313906114f7edda9379b66af7624d9e894bb98812deb7acf5d87ba agents/rocko/work/queue-56/seed.sh
146b236a37bf665979e28f1794c4c4437b6e85feb43b892e8d9d697506fc453f packages/runs/package.json
594eb42518f73c94608320ce381868026aca69d8f5104711c9685ebdd8df41c9 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
c013ed1912bb97cbfb5209170741d9a20c2955a9acbd1fff9e397fac1fab742a packages/runs/src/state.mjs
7c941596020b14d8627ccb09b66e0e51195318e86add60eba9d2c4409e6f8dd6 packages/runs/tests/helpers.mjs
4125ffc8787f3cd9441bc2a557f42fde6948b884b8b870b503acef6eee6b3913 packages/runs/tests/runs.test.mjs
7857d2c8c94011df328dbf5013626dbcdd577945781d104dff533f08b1859130 packages/runs/tests/state.test.mjs
6bf3e758c191e23fddec6bcf30d881cdda1bb37d4e3ed1cd4ef47be79fb36bd8 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/r56a/tmp DOCKER_HOST=unix:///nonexistent.sock
cd ~/darkwing-scratch/r56a/cand
O=~/darkwing-scratch/r56a/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,45 @@
✔ isRunId accepts the run id shape and nothing else (1.129303ms)
✔ a missing data root or runs directory lists nothing (2.363755ms)
✔ runs as a regular file lists nothing, as before (0.81288ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.872847ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.892326ms)
✔ readRunRecord returns documents and artifacts in directory order (1.545032ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.947125ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.861621ms)
✔ readRunDocument reads only the three run documents (0.744194ms)
✔ a link inside the data root is followed (2.213785ms)
✔ a data root that is itself a link is trusted as configured (0.840807ms)
✔ a run directory linked out of the data root is never read (0.832753ms)
✔ a document linked out of the data root reads as null (1.088125ms)
✔ a runs directory linked out of the data root refuses (0.797926ms)
✔ a relative link that climbs out of the data root refuses (0.741627ms)
✔ a sibling whose name starts with the data root's name is outside it (0.847544ms)
✔ a link loop refuses instead of reading as missing (1.021958ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.767933ms)
✔ an unreadable run directory refuses instead of reading as missing (0.687222ms)
✔ a link to the data root's parent is outside it (0.483537ms)
✔ listRunIds sorts whatever order the directory returns (1.065985ms)
✔ the readers write nothing (3.274403ms)
✔ no pointer is null (2.908017ms)
✔ the pointer release.sh writes reads back (1.898894ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (3.141146ms)
✔ a pointer that is a directory refuses with exit 4 (0.931434ms)
✔ a state file linked out of the data root refuses (2.22612ms)
✔ a state directory linked out of the data root refuses (0.984988ms)
✔ a missing log is empty (0.69135ms)
✔ the log reads oldest first and counts malformed lines (1.524874ms)
✔ last must be a positive integer (1.101485ms)
✔ the state readers write nothing (1.595899ms)
✔ list prints each run in the established format (102.108158ms)
✔ show prints the run in the established format (98.414356ms)
✔ show of a missing run and an invalid id exit 4 as before (161.801585ms)
✔ list and show refuse a runs directory linked out of the data root (218.443475ms)
✔ show refuses a run linked out of the data root; list reports it unknown (197.170014ms)
ℹ tests 37
ℹ suites 0
ℹ pass 37
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 845.97083
@@ -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,117 @@
✔ isRunId accepts the run id shape and nothing else (1.956544ms)
✔ a missing data root or runs directory lists nothing (2.725076ms)
✔ runs as a regular file lists nothing, as before (1.059244ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.387509ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.768494ms)
✔ readRunRecord returns documents and artifacts in directory order (1.559464ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.211241ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.141838ms)
✔ readRunDocument reads only the three run documents (0.910973ms)
✔ a link inside the data root is followed (1.969226ms)
✔ a data root that is itself a link is trusted as configured (1.360016ms)
✖ a run directory linked out of the data root is never read (2.59955ms)
✖ a document linked out of the data root reads as null (1.129541ms)
✔ a runs directory linked out of the data root refuses (2.924601ms)
✔ a relative link that climbs out of the data root refuses (0.900903ms)
✔ a sibling whose name starts with the data root's name is outside it (0.987308ms)
✖ a link loop refuses instead of reading as missing (1.102282ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.771326ms)
✔ an unreadable run directory refuses instead of reading as missing (1.015364ms)
✔ a link to the data root's parent is outside it (2.042409ms)
✔ listRunIds sorts whatever order the directory returns (1.683733ms)
✔ the readers write nothing (3.50432ms)
✔ no pointer is null (3.237439ms)
✔ the pointer release.sh writes reads back (2.002114ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (2.94932ms)
✔ a pointer that is a directory refuses with exit 4 (1.676768ms)
✔ a state file linked out of the data root refuses (1.657011ms)
✔ a state directory linked out of the data root refuses (1.062717ms)
✔ a missing log is empty (0.687978ms)
✔ the log reads oldest first and counts malformed lines (1.731535ms)
✔ last must be a positive integer (0.797464ms)
✔ the state readers write nothing (1.515893ms)
✔ list prints each run in the established format (142.248626ms)
✔ show prints the run in the established format (102.945184ms)
✔ show of a missing run and an invalid id exit 4 as before (148.67361ms)
✔ list and show refuse a runs directory linked out of the data root (192.921615ms)
✖ show refuses a run linked out of the data root; list reports it unknown (213.400604ms)
ℹ tests 37
ℹ suites 0
ℹ pass 33
ℹ fail 4
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 871.096655
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:119:1
✖ a run directory linked out of the data root is never read (2.59955ms)
Error [RunsError]: /home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-PvDpe8/data/runs/r-20260101T000000Z-aaaaaa/result.json resolves outside the data root (/home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-PvDpe8/data)
at resolveInside (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/paths.mjs:33:11)
at readJsonObject (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/paths.mjs:42:18)
at file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/runs.mjs:46:13
at Array.map (<anonymous>)
at listRunRecords (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/runs.mjs:44:31)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 (1.129541ms)
Error [RunsError]: /home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-WcZej3/data/runs/r-20260101T000000Z-aaaaaa/result.json resolves outside the data root (/home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-WcZej3/data)
at resolveInside (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/paths.mjs:33:11)
at readJsonObject (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/paths.mjs:42:18)
at readRunDocument (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/runs.mjs:37:10)
at readRunRecord (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/runs.mjs:67:13)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 (1.102282ms)
Error [RunsError]: cannot resolve /home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-x1gAr0/data/runs/r-20260101T000000Z-aaaaaa/result.json: ELOOP
at resolveInside (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/paths.mjs:30:11)
at readJsonObject (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/paths.mjs:42:18)
at file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/runs.mjs:46:13
at Array.map (<anonymous>)
at listRunRecords (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/runs.mjs:44:31)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 (213.400604ms)
AssertionError [ERR_ASSERTION]: mosaic-task: /home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-223gdI/data/runs/r-20260101T000000Z-aaaaaa/result.json resolves outside the data root (/home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-223gdI/data)
4 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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,99 @@
✔ isRunId accepts the run id shape and nothing else (1.68092ms)
✔ a missing data root or runs directory lists nothing (3.048389ms)
✔ runs as a regular file lists nothing, as before (0.963654ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.872482ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (3.199887ms)
✔ readRunRecord returns documents and artifacts in directory order (1.277031ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.810683ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.290617ms)
✔ readRunDocument reads only the three run documents (0.831152ms)
✔ a link inside the data root is followed (1.288609ms)
✔ a data root that is itself a link is trusted as configured (0.929194ms)
✔ a run directory linked out of the data root is never read (1.220122ms)
✔ a document linked out of the data root reads as null (1.495058ms)
✖ a runs directory linked out of the data root refuses (1.812269ms)
✔ a relative link that climbs out of the data root refuses (0.695909ms)
✔ a sibling whose name starts with the data root's name is outside it (0.712215ms)
✔ a link loop refuses instead of reading as missing (0.934066ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.655307ms)
✔ an unreadable run directory refuses instead of reading as missing (0.742938ms)
✔ a link to the data root's parent is outside it (0.576201ms)
✔ listRunIds sorts whatever order the directory returns (1.174841ms)
✔ the readers write nothing (2.73421ms)
✔ no pointer is null (2.202006ms)
✔ the pointer release.sh writes reads back (1.472039ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (2.816169ms)
✔ a pointer that is a directory refuses with exit 4 (1.423693ms)
✔ a state file linked out of the data root refuses (1.147291ms)
✔ a state directory linked out of the data root refuses (0.647667ms)
✔ a missing log is empty (1.318528ms)
✔ the log reads oldest first and counts malformed lines (1.349691ms)
✔ last must be a positive integer (0.767877ms)
✔ the state readers write nothing (1.128232ms)
✔ list prints each run in the established format (118.264972ms)
✔ show prints the run in the established format (82.606742ms)
✔ show of a missing run and an invalid id exit 4 as before (129.149189ms)
✖ list and show refuse a runs directory linked out of the data root (187.309472ms)
✔ show refuses a run linked out of the data root; list reports it unknown (222.898306ms)
ℹ tests 37
ℹ suites 0
ℹ pass 35
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 812.977588
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:143:1
✖ a runs directory linked out of the data root refuses (1.812269ms)
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /runs resolves outside the data root/. Input:
'RunsError: /home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-zX0oF7/data/runs/r-20260101T000000Z-aaaaaa resolves outside the data root (/home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-zX0oF7/data)'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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/r56a/tmp/mosaic-runs-test-zX0oF7/data/runs/r-20260101T000000Z-aaaaaa resolves outside the data root (/home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-zX0oF7/data)
at resolveInside (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/paths.mjs:33:11)
at readRunRecord (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/runs.mjs:55:15)
at file:///home/jwoltje/darkwing-scratch/r56a/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/r56a/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 (187.309472ms)
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/r56a/tmp/mosaic-runs-test-v5t5hA/data/runs/r-20260101T000000Z-aaaaaa resolves outside the data root (/home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-v5t5hA/data)\n'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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/r56a/tmp/mosaic-runs-test-v5t5hA/data/runs/r-20260101T000000Z-aaaaaa resolves outside the data root (/home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-v5t5hA/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 a92c8713..c32758bc 100644
--- a/packages/runs/src/state.mjs
+++ b/packages/runs/src/state.mjs
@@ -78,5 +78,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,94 @@
✔ isRunId accepts the run id shape and nothing else (1.680565ms)
✔ a missing data root or runs directory lists nothing (1.794757ms)
✔ runs as a regular file lists nothing, as before (0.731147ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.864372ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (3.326643ms)
✔ readRunRecord returns documents and artifacts in directory order (1.783473ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.188716ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.093319ms)
✔ readRunDocument reads only the three run documents (0.846733ms)
✔ a link inside the data root is followed (1.17458ms)
✔ a data root that is itself a link is trusted as configured (0.781634ms)
✔ a run directory linked out of the data root is never read (0.777016ms)
✔ a document linked out of the data root reads as null (1.278597ms)
✔ a runs directory linked out of the data root refuses (0.649479ms)
✔ a relative link that climbs out of the data root refuses (0.502717ms)
✔ a sibling whose name starts with the data root's name is outside it (0.58152ms)
✔ a link loop refuses instead of reading as missing (0.775651ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.659139ms)
✔ an unreadable run directory refuses instead of reading as missing (1.013293ms)
✔ a link to the data root's parent is outside it (0.653539ms)
✔ listRunIds sorts whatever order the directory returns (1.135619ms)
✔ the readers write nothing (2.823246ms)
✔ no pointer is null (2.918785ms)
✔ the pointer release.sh writes reads back (1.860026ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (2.823183ms)
✔ a pointer that is a directory refuses with exit 4 (1.736666ms)
✔ a state file linked out of the data root refuses (1.677527ms)
✔ a state directory linked out of the data root refuses (0.987759ms)
✔ a missing log is empty (0.672303ms)
✖ the log reads oldest first and counts malformed lines (3.332551ms)
✔ last must be a positive integer (0.804321ms)
✔ the state readers write nothing (2.095782ms)
✔ list prints each run in the established format (98.447736ms)
✔ show prints the run in the established format (94.546953ms)
✔ show of a missing run and an invalid id exit 4 as before (141.127685ms)
✔ list and show refuse a runs directory linked out of the data root (186.311477ms)
✔ show refuses a run linked out of the data root; list reports it unknown (200.292033ms)
ℹ tests 37
ℹ suites 0
ℹ pass 36
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 810.059294
✖ failing tests:
test at packages/runs/tests/state.test.mjs:80:1
✖ the log reads oldest first and counts malformed lines (3.332551ms)
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: 5
}
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/tests/state.test.mjs:102: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: 5 },
expected: { entries: [ [Object], [Object] ], malformed: 5 },
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/runs/src/state.mjs b/packages/runs/src/state.mjs
index a92c8713..1002fb94 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,93 @@
✔ isRunId accepts the run id shape and nothing else (1.849966ms)
✔ a missing data root or runs directory lists nothing (1.966701ms)
✔ runs as a regular file lists nothing, as before (1.044085ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.404037ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (3.671713ms)
✔ readRunRecord returns documents and artifacts in directory order (1.978123ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.320202ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.202015ms)
✔ readRunDocument reads only the three run documents (0.982657ms)
✔ a link inside the data root is followed (1.840354ms)
✔ a data root that is itself a link is trusted as configured (1.271595ms)
✔ a run directory linked out of the data root is never read (1.363834ms)
✔ a document linked out of the data root reads as null (1.561635ms)
✔ a runs directory linked out of the data root refuses (0.727258ms)
✔ a relative link that climbs out of the data root refuses (0.670099ms)
✔ a sibling whose name starts with the data root's name is outside it (0.641573ms)
✔ a link loop refuses instead of reading as missing (0.934577ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.850297ms)
✔ an unreadable run directory refuses instead of reading as missing (0.87724ms)
✔ a link to the data root's parent is outside it (0.770198ms)
✔ listRunIds sorts whatever order the directory returns (1.036885ms)
✔ the readers write nothing (3.831769ms)
✔ no pointer is null (2.316454ms)
✔ the pointer release.sh writes reads back (1.598365ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (2.175811ms)
✔ a pointer that is a directory refuses with exit 4 (0.778144ms)
✔ a state file linked out of the data root refuses (2.517371ms)
✔ a state directory linked out of the data root refuses (0.870816ms)
✔ a missing log is empty (0.611372ms)
✖ the log reads oldest first and counts malformed lines (2.862969ms)
✔ last must be a positive integer (0.64267ms)
✔ the state readers write nothing (1.472105ms)
✔ list prints each run in the established format (103.761545ms)
✔ show prints the run in the established format (97.443115ms)
✔ show of a missing run and an invalid id exit 4 as before (161.288152ms)
✔ list and show refuse a runs directory linked out of the data root (176.117228ms)
✔ show refuses a run linked out of the data root; list reports it unknown (238.782496ms)
ℹ tests 37
ℹ suites 0
ℹ pass 36
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 865.082532
✖ failing tests:
test at packages/runs/tests/state.test.mjs:80:1
✖ the log reads oldest first and counts malformed lines (2.862969ms)
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: 4
- malformed: 5
}
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/tests/state.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: { entries: [ [Object], [Object], [Object], [Object], [Object] ], malformed: 4 },
expected: { entries: [ [Object], [Object], [Object], [Object] ], malformed: 5 },
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,64 @@
✔ isRunId accepts the run id shape and nothing else (1.74403ms)
✔ a missing data root or runs directory lists nothing (2.198048ms)
✔ runs as a regular file lists nothing, as before (0.749386ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.943488ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (4.593534ms)
✔ readRunRecord returns documents and artifacts in directory order (2.041033ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.316339ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.130767ms)
✔ readRunDocument reads only the three run documents (0.938903ms)
✔ a link inside the data root is followed (1.911798ms)
✔ a data root that is itself a link is trusted as configured (1.338795ms)
✔ a run directory linked out of the data root is never read (1.316168ms)
✔ a document linked out of the data root reads as null (1.553329ms)
✔ a runs directory linked out of the data root refuses (0.940651ms)
✔ a relative link that climbs out of the data root refuses (0.763816ms)
✔ a sibling whose name starts with the data root's name is outside it (0.831365ms)
✔ a link loop refuses instead of reading as missing (1.081862ms)
✖ an unreadable runs directory refuses instead of listing nothing (1.920097ms)
✔ an unreadable run directory refuses instead of reading as missing (0.910463ms)
✔ a link to the data root's parent is outside it (0.731845ms)
✔ listRunIds sorts whatever order the directory returns (1.261893ms)
✔ the readers write nothing (4.021246ms)
✔ no pointer is null (3.059529ms)
✔ the pointer release.sh writes reads back (1.661181ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (2.521897ms)
✔ a pointer that is a directory refuses with exit 4 (1.465131ms)
✔ a state file linked out of the data root refuses (1.638679ms)
✔ a state directory linked out of the data root refuses (0.691235ms)
✔ a missing log is empty (0.580753ms)
✔ the log reads oldest first and counts malformed lines (1.082188ms)
✔ last must be a positive integer (0.648072ms)
✔ the state readers write nothing (1.944511ms)
✔ list prints each run in the established format (135.505629ms)
✔ show prints the run in the established format (124.440305ms)
✔ show of a missing run and an invalid id exit 4 as before (139.637014ms)
✔ list and show refuse a runs directory linked out of the data root (207.914443ms)
✔ show refuses a run linked out of the data root; list reports it unknown (222.421367ms)
ℹ tests 37
ℹ suites 0
ℹ pass 36
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 912.133056
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:177:1
✖ an unreadable runs directory refuses instead of listing nothing (1.920097ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 a92c8713..1c7c4378 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,64 @@
✔ isRunId accepts the run id shape and nothing else (2.461841ms)
✔ a missing data root or runs directory lists nothing (4.122833ms)
✔ runs as a regular file lists nothing, as before (1.049435ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.484169ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (5.953769ms)
✔ readRunRecord returns documents and artifacts in directory order (2.533866ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.781628ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.272589ms)
✔ readRunDocument reads only the three run documents (1.222564ms)
✔ a link inside the data root is followed (2.349315ms)
✔ a data root that is itself a link is trusted as configured (4.990435ms)
✔ a run directory linked out of the data root is never read (1.847834ms)
✔ a document linked out of the data root reads as null (1.825285ms)
✔ a runs directory linked out of the data root refuses (1.095073ms)
✔ a relative link that climbs out of the data root refuses (0.798642ms)
✔ a sibling whose name starts with the data root's name is outside it (0.914727ms)
✔ a link loop refuses instead of reading as missing (2.417719ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.968089ms)
✔ an unreadable run directory refuses instead of reading as missing (1.041544ms)
✔ a link to the data root's parent is outside it (0.822282ms)
✔ listRunIds sorts whatever order the directory returns (1.693855ms)
✔ the readers write nothing (4.240004ms)
✔ no pointer is null (4.711471ms)
✔ the pointer release.sh writes reads back (3.19144ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (3.698419ms)
✖ a pointer that is a directory refuses with exit 4 (2.271643ms)
✔ a state file linked out of the data root refuses (3.820894ms)
✔ a state directory linked out of the data root refuses (1.328242ms)
✔ a missing log is empty (0.757558ms)
✔ the log reads oldest first and counts malformed lines (3.341732ms)
✔ last must be a positive integer (1.01732ms)
✔ the state readers write nothing (2.075785ms)
✔ list prints each run in the established format (134.172955ms)
✔ show prints the run in the established format (111.604381ms)
✔ show of a missing run and an invalid id exit 4 as before (176.970683ms)
✔ list and show refuse a runs directory linked out of the data root (212.899331ms)
✔ show refuses a run linked out of the data root; list reports it unknown (174.178303ms)
ℹ tests 37
ℹ suites 0
ℹ pass 36
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 930.307733
✖ failing tests:
test at packages/runs/tests/state.test.mjs:51:1
✖ a pointer that is a directory refuses with exit 4 (2.271643ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 a92c8713..9e424f9f 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,64 @@
✔ isRunId accepts the run id shape and nothing else (1.76778ms)
✔ a missing data root or runs directory lists nothing (2.441016ms)
✔ runs as a regular file lists nothing, as before (0.970565ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.262129ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (3.361776ms)
✔ readRunRecord returns documents and artifacts in directory order (1.832576ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.389704ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.129822ms)
✔ readRunDocument reads only the three run documents (0.922434ms)
✔ a link inside the data root is followed (1.811731ms)
✔ a data root that is itself a link is trusted as configured (1.209871ms)
✔ a run directory linked out of the data root is never read (1.162347ms)
✔ a document linked out of the data root reads as null (1.753983ms)
✔ a runs directory linked out of the data root refuses (0.898425ms)
✔ a relative link that climbs out of the data root refuses (0.561041ms)
✔ a sibling whose name starts with the data root's name is outside it (0.862277ms)
✔ a link loop refuses instead of reading as missing (0.825058ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.584625ms)
✔ an unreadable run directory refuses instead of reading as missing (0.750071ms)
✔ a link to the data root's parent is outside it (0.445297ms)
✔ listRunIds sorts whatever order the directory returns (0.954512ms)
✔ the readers write nothing (2.660378ms)
✔ no pointer is null (2.198032ms)
✔ the pointer release.sh writes reads back (1.29844ms)
✖ a pointer that isn't the version 1 shape refuses with exit 2 (8.313848ms)
✔ a pointer that is a directory refuses with exit 4 (1.040667ms)
✔ a state file linked out of the data root refuses (2.188204ms)
✔ a state directory linked out of the data root refuses (0.785082ms)
✔ a missing log is empty (0.511055ms)
✔ the log reads oldest first and counts malformed lines (1.260842ms)
✔ last must be a positive integer (0.5446ms)
✔ the state readers write nothing (1.511675ms)
✔ list prints each run in the established format (106.978183ms)
✔ show prints the run in the established format (82.075624ms)
✔ show of a missing run and an invalid id exit 4 as before (150.338163ms)
✔ list and show refuse a runs directory linked out of the data root (213.344597ms)
✔ show refuses a run linked out of the data root; list reports it unknown (189.567749ms)
ℹ tests 37
ℹ suites 0
ℹ pass 36
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 817.276399
✖ 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 (8.313848ms)
AssertionError [ERR_ASSERTION]: Missing expected exception: /unsupported key: "extra"/
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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,45 @@
✔ isRunId accepts the run id shape and nothing else (1.51662ms)
✔ a missing data root or runs directory lists nothing (2.475089ms)
✔ runs as a regular file lists nothing, as before (1.23105ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.353463ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (3.784699ms)
✔ readRunRecord returns documents and artifacts in directory order (1.983165ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.313701ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.315203ms)
✔ readRunDocument reads only the three run documents (1.070086ms)
✔ a link inside the data root is followed (1.390231ms)
✔ a data root that is itself a link is trusted as configured (1.13756ms)
✔ a run directory linked out of the data root is never read (1.289428ms)
✔ a document linked out of the data root reads as null (1.707909ms)
✔ a runs directory linked out of the data root refuses (0.751734ms)
✔ a relative link that climbs out of the data root refuses (0.807517ms)
✔ a sibling whose name starts with the data root's name is outside it (0.930121ms)
✔ a link loop refuses instead of reading as missing (4.279406ms)
✔ an unreadable runs directory refuses instead of listing nothing (1.054034ms)
✔ an unreadable run directory refuses instead of reading as missing (1.037179ms)
✔ a link to the data root's parent is outside it (0.766076ms)
✔ listRunIds sorts whatever order the directory returns (1.561169ms)
✔ the readers write nothing (4.251191ms)
✔ no pointer is null (2.961101ms)
✔ the pointer release.sh writes reads back (2.02442ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (5.513029ms)
✔ a pointer that is a directory refuses with exit 4 (1.812389ms)
✔ a state file linked out of the data root refuses (1.807408ms)
✔ a state directory linked out of the data root refuses (1.064741ms)
✔ a missing log is empty (0.778107ms)
✔ the log reads oldest first and counts malformed lines (1.719825ms)
✔ last must be a positive integer (0.79633ms)
✔ the state readers write nothing (1.960078ms)
✔ list prints each run in the established format (121.641214ms)
✔ show prints the run in the established format (103.243231ms)
✔ show of a missing run and an invalid id exit 4 as before (168.245538ms)
✔ list and show refuse a runs directory linked out of the data root (247.465101ms)
✔ show refuses a run linked out of the data root; list reports it unknown (182.280266ms)
ℹ tests 37
ℹ suites 0
ℹ pass 37
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 914.239511
@@ -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,89 @@
✔ isRunId accepts the run id shape and nothing else (1.335247ms)
✔ a missing data root or runs directory lists nothing (2.044238ms)
✔ runs as a regular file lists nothing, as before (0.825059ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.992615ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.448335ms)
✔ readRunRecord returns documents and artifacts in directory order (1.940595ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.88747ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.887944ms)
✔ readRunDocument reads only the three run documents (0.742112ms)
✔ a link inside the data root is followed (1.800967ms)
✔ a data root that is itself a link is trusted as configured (1.347045ms)
✔ a run directory linked out of the data root is never read (1.219752ms)
✔ a document linked out of the data root reads as null (1.142642ms)
✔ a runs directory linked out of the data root refuses (0.77569ms)
✔ a relative link that climbs out of the data root refuses (0.508687ms)
✔ a sibling whose name starts with the data root's name is outside it (0.818208ms)
✔ a link loop refuses instead of reading as missing (0.874875ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.617573ms)
✔ an unreadable run directory refuses instead of reading as missing (0.789617ms)
✔ a link to the data root's parent is outside it (0.744195ms)
✔ listRunIds sorts whatever order the directory returns (1.123154ms)
✔ the readers write nothing (3.640519ms)
✔ no pointer is null (2.825548ms)
✔ the pointer release.sh writes reads back (1.614689ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (3.000458ms)
✔ a pointer that is a directory refuses with exit 4 (0.784002ms)
✔ a state file linked out of the data root refuses (2.224001ms)
✔ a state directory linked out of the data root refuses (1.043958ms)
✔ a missing log is empty (0.707165ms)
✔ the log reads oldest first and counts malformed lines (1.568329ms)
✔ last must be a positive integer (0.801038ms)
✔ the state readers write nothing (1.64136ms)
✔ list prints each run in the established format (114.475779ms)
✔ show prints the run in the established format (92.906292ms)
✔ show of a missing run and an invalid id exit 4 as before (173.27576ms)
✖ list and show refuse a runs directory linked out of the data root (104.319544ms)
✖ show refuses a run linked out of the data root; list reports it unknown (123.359118ms)
ℹ tests 37
ℹ suites 0
ℹ pass 35
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 689.166126
✖ 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 (104.319544ms)
AssertionError [ERR_ASSERTION]: list
1 !== 4
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 (123.359118ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
1 !== 4
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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,61 @@
✔ isRunId accepts the run id shape and nothing else (1.279176ms)
✔ a missing data root or runs directory lists nothing (2.009751ms)
✔ runs as a regular file lists nothing, as before (0.780142ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.907817ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.573369ms)
✔ readRunRecord returns documents and artifacts in directory order (1.415964ms)
✖ readRunRecord is null for a missing run, a dangling link or a file (1.425009ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.074088ms)
✔ readRunDocument reads only the three run documents (0.943201ms)
✔ a link inside the data root is followed (1.78009ms)
✔ a data root that is itself a link is trusted as configured (1.185862ms)
✔ a run directory linked out of the data root is never read (1.204155ms)
✔ a document linked out of the data root reads as null (1.549239ms)
✔ a runs directory linked out of the data root refuses (1.03995ms)
✔ a relative link that climbs out of the data root refuses (0.689295ms)
✔ a sibling whose name starts with the data root's name is outside it (0.923991ms)
✔ a link loop refuses instead of reading as missing (1.150588ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.729845ms)
✔ an unreadable run directory refuses instead of reading as missing (0.891325ms)
✔ a link to the data root's parent is outside it (1.221017ms)
✔ listRunIds sorts whatever order the directory returns (1.108483ms)
✔ the readers write nothing (3.377499ms)
✔ no pointer is null (2.369876ms)
✔ the pointer release.sh writes reads back (1.445943ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (4.334902ms)
✔ a pointer that is a directory refuses with exit 4 (0.757318ms)
✔ a state file linked out of the data root refuses (1.27317ms)
✔ a state directory linked out of the data root refuses (0.743687ms)
✔ a missing log is empty (0.463828ms)
✔ the log reads oldest first and counts malformed lines (1.107084ms)
✔ last must be a positive integer (0.692277ms)
✔ the state readers write nothing (1.18172ms)
✔ list prints each run in the established format (105.980567ms)
✔ show prints the run in the established format (115.012534ms)
✔ show of a missing run and an invalid id exit 4 as before (135.038631ms)
✔ list and show refuse a runs directory linked out of the data root (172.97353ms)
✔ show refuses a run linked out of the data root; list reports it unknown (197.370536ms)
ℹ tests 37
ℹ suites 0
ℹ pass 36
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 805.379529
✖ 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 (1.425009ms)
Error [RunsError]: cannot read /home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-FPVVtO/data/runs/r-20260101T000100Z-bbbbbb: ENOTDIR
at readRunRecord (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/runs.mjs:62:11)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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,334 @@
✔ isRunId accepts the run id shape and nothing else (2.337061ms)
✔ a missing data root or runs directory lists nothing (2.812348ms)
✔ runs as a regular file lists nothing, as before (0.989212ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.36075ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (3.67149ms)
✔ readRunRecord returns documents and artifacts in directory order (2.491353ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.899895ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.568632ms)
✔ readRunDocument reads only the three run documents (1.626551ms)
✔ a link inside the data root is followed (1.731397ms)
✖ a data root that is itself a link is trusted as configured (1.465017ms)
✖ a run directory linked out of the data root is never read (3.090379ms)
✖ a document linked out of the data root reads as null (1.761344ms)
✖ a runs directory linked out of the data root refuses (0.967405ms)
✖ a relative link that climbs out of the data root refuses (1.918539ms)
✖ a sibling whose name starts with the data root's name is outside it (1.08471ms)
✔ a link loop refuses instead of reading as missing (1.050719ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.697011ms)
✔ an unreadable run directory refuses instead of reading as missing (0.878691ms)
✖ a link to the data root's parent is outside it (0.945372ms)
✔ listRunIds sorts whatever order the directory returns (1.249209ms)
✔ the readers write nothing (2.766612ms)
✖ no pointer is null (3.426697ms)
✔ the pointer release.sh writes reads back (2.049083ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (3.05565ms)
✔ a pointer that is a directory refuses with exit 4 (1.036013ms)
✖ a state file linked out of the data root refuses (2.970028ms)
✖ a state directory linked out of the data root refuses (2.142321ms)
✖ a missing log is empty (0.990974ms)
✔ the log reads oldest first and counts malformed lines (2.655752ms)
✔ last must be a positive integer (0.876981ms)
✔ the state readers write nothing (2.744ms)
✔ list prints each run in the established format (170.137522ms)
✔ show prints the run in the established format (120.109446ms)
✔ show of a missing run and an invalid id exit 4 as before (151.012044ms)
✖ list and show refuse a runs directory linked out of the data root (124.06692ms)
✖ show refuses a run linked out of the data root; list reports it unknown (140.926544ms)
ℹ tests 37
ℹ suites 0
ℹ pass 24
ℹ fail 13
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 811.148685
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:111:1
✖ a data root that is itself a link is trusted as configured (1.465017ms)
Error [RunsError]: /home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-4NH1n7/alias/runs resolves outside the data root (/home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-4NH1n7/data)
at resolveInside (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/paths.mjs:33:11)
at listRunIds (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/runs.mjs:20:16)
at listRunRecords (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/runs.mjs:44:10)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 (3.090379ms)
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/r56a/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 (1.761344ms)
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/r56a/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.967405ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 (1.918539ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 (1.08471ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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.945372ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 (3.426697ms)
Error [RunsError]: cannot read /home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-jLz66S/data/state/active.json: ENOENT
at readStateFile (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/state.mjs:21:11)
at readActivePointer (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/state.mjs:29:17)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 (2.970028ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 (2.142321ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 missing log is empty (0.990974ms)
Error [RunsError]: cannot read /home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-2x3xPI/data/state/activation-log.jsonl: ENOENT
at readStateFile (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/state.mjs:21:11)
at readActivationLog (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/state.mjs:71:17)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/tests/state.test.mjs:77: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 (124.06692ms)
AssertionError [ERR_ASSERTION]: list
0 !== 4
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 (140.926544ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 4
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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,63 @@
✔ isRunId accepts the run id shape and nothing else (1.225949ms)
✔ a missing data root or runs directory lists nothing (1.892082ms)
✔ runs as a regular file lists nothing, as before (0.747282ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.931027ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.487142ms)
✔ readRunRecord returns documents and artifacts in directory order (1.153245ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.788032ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.781425ms)
✔ readRunDocument reads only the three run documents (0.64425ms)
✔ a link inside the data root is followed (1.443248ms)
✖ a data root that is itself a link is trusted as configured (0.723306ms)
✔ a run directory linked out of the data root is never read (0.774806ms)
✔ a document linked out of the data root reads as null (1.18996ms)
✔ a runs directory linked out of the data root refuses (0.624807ms)
✔ a relative link that climbs out of the data root refuses (0.520406ms)
✔ a sibling whose name starts with the data root's name is outside it (0.623168ms)
✔ a link loop refuses instead of reading as missing (0.641501ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.470743ms)
✔ an unreadable run directory refuses instead of reading as missing (0.559037ms)
✔ a link to the data root's parent is outside it (0.503187ms)
✔ listRunIds sorts whatever order the directory returns (16.993379ms)
✔ the readers write nothing (3.04089ms)
✔ no pointer is null (2.102694ms)
✔ the pointer release.sh writes reads back (1.420335ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (99.446402ms)
✔ a pointer that is a directory refuses with exit 4 (1.965912ms)
✔ a state file linked out of the data root refuses (1.584736ms)
✔ a state directory linked out of the data root refuses (0.926403ms)
✔ a missing log is empty (0.585862ms)
✔ the log reads oldest first and counts malformed lines (1.76685ms)
✔ last must be a positive integer (0.522534ms)
✔ the state readers write nothing (1.138156ms)
✔ list prints each run in the established format (99.14821ms)
✔ show prints the run in the established format (112.530433ms)
✔ show of a missing run and an invalid id exit 4 as before (168.188718ms)
✔ list and show refuse a runs directory linked out of the data root (200.199186ms)
✔ show refuses a run linked out of the data root; list reports it unknown (210.660658ms)
ℹ tests 37
ℹ suites 0
ℹ pass 36
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 877.697783
✖ 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.723306ms)
Error [RunsError]: /home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-5lxeUT/alias/runs resolves outside the data root (/home/jwoltje/darkwing-scratch/r56a/tmp/mosaic-runs-test-5lxeUT/alias)
at resolveInside (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/paths.mjs:33:11)
at listRunIds (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/runs.mjs:20:16)
at listRunRecords (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/src/runs.mjs:44:10)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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,66 @@
✖ isRunId accepts the run id shape and nothing else (2.621121ms)
✔ a missing data root or runs directory lists nothing (2.873428ms)
✔ runs as a regular file lists nothing, as before (0.877432ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.2193ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (4.17104ms)
✔ readRunRecord returns documents and artifacts in directory order (2.167047ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.610572ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.237909ms)
✔ readRunDocument reads only the three run documents (1.490464ms)
✔ a link inside the data root is followed (2.047317ms)
✔ a data root that is itself a link is trusted as configured (1.310886ms)
✔ a run directory linked out of the data root is never read (1.375476ms)
✔ a document linked out of the data root reads as null (1.919519ms)
✔ a runs directory linked out of the data root refuses (1.177495ms)
✔ a relative link that climbs out of the data root refuses (0.809767ms)
✔ a sibling whose name starts with the data root's name is outside it (0.918443ms)
✔ a link loop refuses instead of reading as missing (1.269903ms)
✔ an unreadable runs directory refuses instead of listing nothing (1.13428ms)
✔ an unreadable run directory refuses instead of reading as missing (1.208613ms)
✔ a link to the data root's parent is outside it (1.309226ms)
✔ listRunIds sorts whatever order the directory returns (1.665016ms)
✔ the readers write nothing (4.268303ms)
✔ no pointer is null (2.482055ms)
✔ the pointer release.sh writes reads back (1.622475ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (2.093312ms)
✔ a pointer that is a directory refuses with exit 4 (1.37517ms)
✔ a state file linked out of the data root refuses (1.304846ms)
✔ a state directory linked out of the data root refuses (0.707381ms)
✔ a missing log is empty (0.514771ms)
✔ the log reads oldest first and counts malformed lines (1.170055ms)
✔ last must be a positive integer (0.690868ms)
✔ the state readers write nothing (1.127808ms)
✔ list prints each run in the established format (111.188746ms)
✔ show prints the run in the established format (107.486029ms)
✔ show of a missing run and an invalid id exit 4 as before (143.908228ms)
✔ list and show refuse a runs directory linked out of the data root (191.94812ms)
✔ show refuses a run linked out of the data root; list reports it unknown (212.7919ms)
ℹ tests 37
ℹ suites 0
ℹ pass 36
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 858.033888
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:13:1
✖ isRunId accepts the run id shape and nothing else (2.621121ms)
AssertionError [ERR_ASSERTION]: r-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
true !== false
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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,45 @@
✔ isRunId accepts the run id shape and nothing else (1.84367ms)
✔ a missing data root or runs directory lists nothing (2.700807ms)
✔ runs as a regular file lists nothing, as before (1.033316ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.32646ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (3.42376ms)
✔ readRunRecord returns documents and artifacts in directory order (1.65341ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.089868ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.018263ms)
✔ readRunDocument reads only the three run documents (2.607684ms)
✔ a link inside the data root is followed (1.72714ms)
✔ a data root that is itself a link is trusted as configured (1.101493ms)
✔ a run directory linked out of the data root is never read (1.204336ms)
✔ a document linked out of the data root reads as null (2.271677ms)
✔ a runs directory linked out of the data root refuses (0.930102ms)
✔ a relative link that climbs out of the data root refuses (0.643793ms)
✔ a sibling whose name starts with the data root's name is outside it (0.780072ms)
✔ a link loop refuses instead of reading as missing (1.056981ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.657066ms)
✔ an unreadable run directory refuses instead of reading as missing (0.82135ms)
✔ a link to the data root's parent is outside it (0.603085ms)
✔ listRunIds sorts whatever order the directory returns (1.157629ms)
✔ the readers write nothing (3.625933ms)
✔ no pointer is null (2.573356ms)
✔ the pointer release.sh writes reads back (1.601548ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (2.886359ms)
✔ a pointer that is a directory refuses with exit 4 (1.00578ms)
✔ a state file linked out of the data root refuses (1.827811ms)
✔ a state directory linked out of the data root refuses (0.966807ms)
✔ a missing log is empty (0.590111ms)
✔ the log reads oldest first and counts malformed lines (1.749904ms)
✔ last must be a positive integer (0.741643ms)
✔ the state readers write nothing (1.380717ms)
✔ list prints each run in the established format (109.05177ms)
✔ show prints the run in the established format (106.424344ms)
✔ show of a missing run and an invalid id exit 4 as before (151.998567ms)
✔ list and show refuse a runs directory linked out of the data root (198.84273ms)
✔ show refuses a run linked out of the data root; list reports it unknown (214.31565ms)
ℹ tests 37
ℹ suites 0
ℹ pass 37
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 861.341071
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/state.mjs b/packages/runs/src/state.mjs
index a92c8713..067bd09e 100644
--- a/packages/runs/src/state.mjs
+++ b/packages/runs/src/state.mjs
@@ -73,7 +73,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,79 @@
✔ isRunId accepts the run id shape and nothing else (1.81005ms)
✔ a missing data root or runs directory lists nothing (3.788851ms)
✔ runs as a regular file lists nothing, as before (1.332528ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.521814ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (4.886328ms)
✔ readRunRecord returns documents and artifacts in directory order (3.564755ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (2.859036ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.951484ms)
✔ readRunDocument reads only the three run documents (1.289383ms)
✔ a link inside the data root is followed (3.726081ms)
✔ a data root that is itself a link is trusted as configured (5.725889ms)
✔ a run directory linked out of the data root is never read (2.611536ms)
✔ a document linked out of the data root reads as null (2.941983ms)
✔ a runs directory linked out of the data root refuses (1.42401ms)
✔ a relative link that climbs out of the data root refuses (0.981488ms)
✔ a sibling whose name starts with the data root's name is outside it (1.093183ms)
✔ a link loop refuses instead of reading as missing (1.426419ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.900191ms)
✔ an unreadable run directory refuses instead of reading as missing (1.392808ms)
✔ a link to the data root's parent is outside it (0.919184ms)
✔ listRunIds sorts whatever order the directory returns (1.382679ms)
✔ the readers write nothing (3.80633ms)
✔ no pointer is null (3.365628ms)
✔ the pointer release.sh writes reads back (2.160411ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (4.729941ms)
✔ a pointer that is a directory refuses with exit 4 (2.975348ms)
✔ a state file linked out of the data root refuses (1.960951ms)
✔ a state directory linked out of the data root refuses (1.362152ms)
✔ a missing log is empty (0.915277ms)
✖ the log reads oldest first and counts malformed lines (4.787537ms)
✔ last must be a positive integer (1.174335ms)
✔ the state readers write nothing (8.296235ms)
✔ list prints each run in the established format (135.967139ms)
✔ show prints the run in the established format (126.514675ms)
✔ show of a missing run and an invalid id exit 4 as before (248.881535ms)
✔ list and show refuse a runs directory linked out of the data root (178.634661ms)
✔ show refuses a run linked out of the data root; list reports it unknown (318.758144ms)
ℹ tests 37
ℹ suites 0
ℹ pass 36
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1106.083123
✖ failing tests:
test at packages/runs/tests/state.test.mjs:80:1
✖ the log reads oldest first and counts malformed lines (4.787537ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
{
entries: [
{
at: '2026-09-03T20:57:00Z',
event: 'package',
...
],
+ malformed: 6
- malformed: 5
}
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/tests/state.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: { entries: [ [Object], [Object], [Object], [Object] ], malformed: 6 },
expected: { entries: [ [Object], [Object], [Object], [Object] ], malformed: 5 },
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,64 @@
✔ isRunId accepts the run id shape and nothing else (1.177262ms)
✔ a missing data root or runs directory lists nothing (2.120403ms)
✔ runs as a regular file lists nothing, as before (0.678388ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.861957ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.547546ms)
✔ readRunRecord returns documents and artifacts in directory order (1.538135ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.13107ms)
✖ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.271159ms)
✔ readRunDocument reads only the three run documents (0.654773ms)
✔ a link inside the data root is followed (1.097525ms)
✔ a data root that is itself a link is trusted as configured (0.753622ms)
✔ a run directory linked out of the data root is never read (0.834684ms)
✔ a document linked out of the data root reads as null (1.039271ms)
✔ a runs directory linked out of the data root refuses (0.69181ms)
✔ a relative link that climbs out of the data root refuses (1.855925ms)
✔ a sibling whose name starts with the data root's name is outside it (1.188475ms)
✔ a link loop refuses instead of reading as missing (1.134003ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.730721ms)
✔ an unreadable run directory refuses instead of reading as missing (0.863693ms)
✔ a link to the data root's parent is outside it (0.64587ms)
✔ listRunIds sorts whatever order the directory returns (1.228953ms)
✔ the readers write nothing (4.005022ms)
✔ no pointer is null (3.229517ms)
✔ the pointer release.sh writes reads back (2.000427ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (22.490669ms)
✔ a pointer that is a directory refuses with exit 4 (1.097015ms)
✔ a state file linked out of the data root refuses (3.625438ms)
✔ a state directory linked out of the data root refuses (1.557756ms)
✔ a missing log is empty (0.857274ms)
✔ the log reads oldest first and counts malformed lines (1.909144ms)
✔ last must be a positive integer (0.916328ms)
✔ the state readers write nothing (2.334238ms)
✔ list prints each run in the established format (112.749358ms)
✔ show prints the run in the established format (93.106249ms)
✔ show of a missing run and an invalid id exit 4 as before (149.979418ms)
✔ list and show refuse a runs directory linked out of the data root (171.947155ms)
✔ show refuses a run linked out of the data root; list reports it unknown (302.989078ms)
ℹ tests 37
ℹ suites 0
ℹ pass 36
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 900.360942
✖ failing tests:
test at packages/runs/tests/runs.test.mjs:86:1
✖ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.271159ms)
AssertionError [ERR_ASSERTION]: Missing expected exception (RunsError).
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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,45 @@
✔ isRunId accepts the run id shape and nothing else (1.306378ms)
✔ a missing data root or runs directory lists nothing (2.195703ms)
✔ runs as a regular file lists nothing, as before (0.832745ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.032055ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.96098ms)
✔ readRunRecord returns documents and artifacts in directory order (1.386366ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.079913ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.059646ms)
✔ readRunDocument reads only the three run documents (0.919524ms)
✔ a link inside the data root is followed (1.7667ms)
✔ a data root that is itself a link is trusted as configured (1.154799ms)
✔ a run directory linked out of the data root is never read (1.194447ms)
✔ a document linked out of the data root reads as null (1.545639ms)
✔ a runs directory linked out of the data root refuses (1.012007ms)
✔ a relative link that climbs out of the data root refuses (0.840459ms)
✔ a sibling whose name starts with the data root's name is outside it (0.862225ms)
✔ a link loop refuses instead of reading as missing (1.091533ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.700403ms)
✔ an unreadable run directory refuses instead of reading as missing (0.823368ms)
✔ a link to the data root's parent is outside it (0.661858ms)
✔ listRunIds sorts whatever order the directory returns (1.208556ms)
✔ the readers write nothing (3.397935ms)
✔ no pointer is null (2.922658ms)
✔ the pointer release.sh writes reads back (1.955593ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (3.059019ms)
✔ a pointer that is a directory refuses with exit 4 (0.993624ms)
✔ a state file linked out of the data root refuses (2.525011ms)
✔ a state directory linked out of the data root refuses (1.000341ms)
✔ a missing log is empty (0.67567ms)
✔ the log reads oldest first and counts malformed lines (1.536433ms)
✔ last must be a positive integer (0.791281ms)
✔ the state readers write nothing (1.583337ms)
✔ list prints each run in the established format (111.618545ms)
✔ show prints the run in the established format (86.235796ms)
✔ show of a missing run and an invalid id exit 4 as before (147.991565ms)
✔ list and show refuse a runs directory linked out of the data root (221.002247ms)
✔ show refuses a run linked out of the data root; list reports it unknown (185.706284ms)
ℹ tests 37
ℹ suites 0
ℹ pass 37
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 897.558738
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/state.mjs b/packages/runs/src/state.mjs
index a92c8713..1789e568 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,64 @@
✔ isRunId accepts the run id shape and nothing else (1.127867ms)
✔ a missing data root or runs directory lists nothing (1.72466ms)
✔ runs as a regular file lists nothing, as before (0.646415ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.82023ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.39311ms)
✔ readRunRecord returns documents and artifacts in directory order (1.164002ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.814071ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.453894ms)
✔ readRunDocument reads only the three run documents (0.658026ms)
✔ a link inside the data root is followed (1.155118ms)
✔ a data root that is itself a link is trusted as configured (0.793186ms)
✔ a run directory linked out of the data root is never read (0.791204ms)
✔ a document linked out of the data root reads as null (0.969944ms)
✔ a runs directory linked out of the data root refuses (0.658788ms)
✔ a relative link that climbs out of the data root refuses (0.519731ms)
✔ a sibling whose name starts with the data root's name is outside it (0.554988ms)
✔ a link loop refuses instead of reading as missing (0.740597ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.508563ms)
✔ an unreadable run directory refuses instead of reading as missing (0.585049ms)
✔ a link to the data root's parent is outside it (0.436398ms)
✔ listRunIds sorts whatever order the directory returns (0.926292ms)
✔ the readers write nothing (2.24403ms)
✔ no pointer is null (2.531076ms)
✔ the pointer release.sh writes reads back (1.633448ms)
✖ a pointer that isn't the version 1 shape refuses with exit 2 (130.614238ms)
✔ a pointer that is a directory refuses with exit 4 (0.786002ms)
✔ a state file linked out of the data root refuses (2.341036ms)
✔ a state directory linked out of the data root refuses (0.805877ms)
✔ a missing log is empty (0.47975ms)
✔ the log reads oldest first and counts malformed lines (1.125972ms)
✔ last must be a positive integer (0.54587ms)
✔ the state readers write nothing (1.23264ms)
✔ list prints each run in the established format (95.611062ms)
✔ show prints the run in the established format (94.055273ms)
✔ show of a missing run and an invalid id exit 4 as before (138.36289ms)
✔ list and show refuse a runs directory linked out of the data root (184.218135ms)
✔ show refuses a run linked out of the data root; list reports it unknown (314.21614ms)
ℹ tests 37
ℹ suites 0
ℹ pass 36
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 897.837373
✖ 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 (130.614238ms)
AssertionError [ERR_ASSERTION]: Missing expected exception: /unsupported pointerVersion 2/
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/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 a92c8713..c76edde2 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,45 @@
✔ isRunId accepts the run id shape and nothing else (1.834258ms)
✔ a missing data root or runs directory lists nothing (2.636105ms)
✔ runs as a regular file lists nothing, as before (2.901636ms)
✔ listRunIds keeps r- names only, sorted oldest first (3.100526ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (6.571513ms)
✔ readRunRecord returns documents and artifacts in directory order (3.744909ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (2.315471ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (2.654758ms)
✔ readRunDocument reads only the three run documents (1.530565ms)
✔ a link inside the data root is followed (3.351352ms)
✔ a data root that is itself a link is trusted as configured (3.308438ms)
✔ a run directory linked out of the data root is never read (1.199258ms)
✔ a document linked out of the data root reads as null (4.723514ms)
✔ a runs directory linked out of the data root refuses (2.543252ms)
✔ a relative link that climbs out of the data root refuses (0.642465ms)
✔ a sibling whose name starts with the data root's name is outside it (0.865087ms)
✔ a link loop refuses instead of reading as missing (2.746857ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.67741ms)
✔ an unreadable run directory refuses instead of reading as missing (0.735708ms)
✔ a link to the data root's parent is outside it (0.592559ms)
✔ listRunIds sorts whatever order the directory returns (2.772116ms)
✔ the readers write nothing (5.349486ms)
✔ no pointer is null (6.038816ms)
✔ the pointer release.sh writes reads back (2.191058ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (4.422889ms)
✔ a pointer that is a directory refuses with exit 4 (3.41713ms)
✔ a state file linked out of the data root refuses (2.798861ms)
✔ a state directory linked out of the data root refuses (2.478415ms)
✔ a missing log is empty (1.394344ms)
✔ the log reads oldest first and counts malformed lines (1.455927ms)
✔ last must be a positive integer (2.396006ms)
✔ the state readers write nothing (1.735056ms)
✔ list prints each run in the established format (131.488575ms)
✔ show prints the run in the established format (92.166358ms)
✔ show of a missing run and an invalid id exit 4 as before (158.462467ms)
✔ list and show refuse a runs directory linked out of the data root (170.034985ms)
✔ show refuses a run linked out of the data root; list reports it unknown (201.838401ms)
ℹ tests 37
ℹ suites 0
ℹ pass 37
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 872.50546
@@ -0,0 +1,13 @@
diff --git a/packages/runs/src/state.mjs b/packages/runs/src/state.mjs
index a92c8713..49d8c1fd 100644
--- a/packages/runs/src/state.mjs
+++ b/packages/runs/src/state.mjs
@@ -65,7 +65,7 @@ function logEntry(line) {
// release.sh rollback skips them. 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,65 @@
✔ isRunId accepts the run id shape and nothing else (2.040623ms)
✔ a missing data root or runs directory lists nothing (3.167511ms)
✔ runs as a regular file lists nothing, as before (1.148056ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.529862ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (4.035385ms)
✔ readRunRecord returns documents and artifacts in directory order (2.356077ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.382285ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.21881ms)
✔ readRunDocument reads only the three run documents (1.081942ms)
✔ a link inside the data root is followed (1.529533ms)
✔ a data root that is itself a link is trusted as configured (1.029457ms)
✔ a run directory linked out of the data root is never read (1.182624ms)
✔ a document linked out of the data root reads as null (1.55228ms)
✔ a runs directory linked out of the data root refuses (0.964271ms)
✔ a relative link that climbs out of the data root refuses (0.735934ms)
✔ a sibling whose name starts with the data root's name is outside it (0.835571ms)
✔ a link loop refuses instead of reading as missing (1.106386ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.726369ms)
✔ an unreadable run directory refuses instead of reading as missing (0.851219ms)
✔ a link to the data root's parent is outside it (0.763768ms)
✔ listRunIds sorts whatever order the directory returns (1.218202ms)
✔ the readers write nothing (3.280544ms)
✔ no pointer is null (2.733337ms)
✔ the pointer release.sh writes reads back (1.378784ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (5.183544ms)
✔ a pointer that is a directory refuses with exit 4 (1.322104ms)
✔ a state file linked out of the data root refuses (1.28962ms)
✔ a state directory linked out of the data root refuses (0.633332ms)
✔ a missing log is empty (0.439431ms)
✔ the log reads oldest first and counts malformed lines (0.988303ms)
✖ last must be a positive integer (1.077906ms)
✔ the state readers write nothing (1.24199ms)
✔ list prints each run in the established format (110.647803ms)
✔ show prints the run in the established format (110.994361ms)
✔ show of a missing run and an invalid id exit 4 as before (129.235851ms)
✔ list and show refuse a runs directory linked out of the data root (177.07234ms)
✔ show refuses a run linked out of the data root; list reports it unknown (199.262239ms)
ℹ tests 37
ℹ suites 0
ℹ pass 36
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 806.760074
✖ failing tests:
test at packages/runs/tests/state.test.mjs:106:1
✖ last must be a positive integer (1.077906ms)
AssertionError [ERR_ASSERTION]: Missing expected exception: 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r56a/mutwt/packages/runs/tests/state.test.mjs:109: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")
@@ -0,0 +1,59 @@
== P1 show ../x with no config file (mutwt carries D01 when run by this script)
base: exit 4
err| mosaic-task: invalid run id: "../x" (expected r-<id>)
cand: exit 4
err| mosaic-task: invalid run id: "../x" (expected r-<id>)
mutwt: exit 3
err| mosaic-task: configuration problem (exit 3); run scripts/bootstrap.sh or fix config.json
== P2 show of a run directory with mode 000
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/r56a/probes/p2/data/runs/r-20260101T000000Z-aaaaaa: EACCES
mutwt: exit 4
err| mosaic-task: cannot read /home/jwoltje/darkwing-scratch/r56a/probes/p2/data/runs/r-20260101T000000Z-aaaaaa: EACCES
== P3 list with r- names that aren't valid run ids
base: exit 0
out| r- unknown task=- ws=- session=-
out| r-.bad succeeded task=t ws=- session=-
cand: exit 0
out| r- unknown task=- ws=- session=-
out| r-.bad succeeded task=t ws=- session=-
mutwt: exit 0
out| r- unknown task=- ws=- session=-
out| r-.bad succeeded task=t ws=- session=-
== P4 runs/ is a link inside the data root
base: exit 0
out| r-20260101T000000Z-aaaaaa succeeded task=t ws=- session=-
out| r-self unknown task=- ws=- session=-
cand: exit 0
out| r-20260101T000000Z-aaaaaa succeeded task=t ws=- session=-
out| r-self unknown task=- ws=- session=-
mutwt: exit 0
out| r-20260101T000000Z-aaaaaa succeeded task=t ws=- session=-
out| r-self unknown task=- ws=- session=-
== P5 state/ linked out of the data root, no active.json or log there
readActivePointer: null
readActivationLog: {"entries":[],"malformed":0}
== P5b same, with active.json outside
readActivePointer: RunsError 4 <D>/state/active.json resolves outside the data root (/home/jwoltje/darkwing-scratch/r56a/probes/p5/data)
== P6 log lines release.sh rollback would still use (an extra key, a non-string imageTag)
{"entries":[],"malformed":2}
DONE
== P3 list under D17
| mosaic-task: invalid run id: "r-" (expected r-<id>)
exit 4
== P3 list under D20
exit 0
+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/r56a/tmp
S=~/darkwing-scratch/r56a; 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-10T16:57:19Z
D01 SURVIVED exit=0 ℹ pass 37 ℹ fail 0
D03 killed exit=1 ℹ pass 33 ℹ fail 4
D04 killed exit=1 ℹ pass 35 ℹ fail 2
D05 killed exit=1 ℹ pass 36 ℹ fail 1
D06 killed exit=1 ℹ pass 36 ℹ fail 1
D07 killed exit=1 ℹ pass 36 ℹ fail 1
D08 killed exit=1 ℹ pass 36 ℹ fail 1
D09 killed exit=1 ℹ pass 36 ℹ fail 1
D10 SURVIVED exit=0 ℹ pass 37 ℹ fail 0
D11 killed exit=1 ℹ pass 35 ℹ fail 2
D13 killed exit=1 ℹ pass 36 ℹ fail 1
D14 killed exit=1 ℹ pass 24 ℹ fail 13
D15 killed exit=1 ℹ pass 36 ℹ fail 1
D16 killed exit=1 ℹ pass 36 ℹ fail 1
D17 SURVIVED exit=0 ℹ pass 37 ℹ fail 0
D18 killed exit=1 ℹ pass 36 ℹ fail 1
D19 killed exit=1 ℹ pass 36 ℹ fail 1
D20 SURVIVED exit=0 ℹ pass 37 ℹ fail 0
D21 killed exit=1 ℹ pass 36 ℹ fail 1
D22 SURVIVED exit=0 ℹ pass 37 ℹ fail 0
D23 killed exit=1 ℹ pass 36 ℹ fail 1
end 2026-10-10T16:57:40Z
DONE
@@ -0,0 +1,2 @@
byte-check: 16 cases, stdout, stderr and exit identical
byte-check: data root unchanged
@@ -0,0 +1,110 @@
== 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/r56a/deltacheck/run-link/data/runs/r-20260101T000000Z-aaaaaa resolves outside the data root (/home/jwoltje/darkwing-scratch/r56a/deltacheck/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/r56a/deltacheck/runs-link/data/runs resolves outside the data root (/home/jwoltje/darkwing-scratch/r56a/deltacheck/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/r56a/deltacheck/runs-link/data/runs resolves outside the data root (/home/jwoltje/darkwing-scratch/r56a/deltacheck/runs-link/data)
== result.json is 5 and task.json is []: list
base: exit 1
err| file:///home/jwoltje/darkwing-scratch/r56a/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/r56a/deltacheck/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/r56a/deltacheck/loop/data/runs)
cand: exit 4
err| mosaic-task: cannot resolve /home/jwoltje/darkwing-scratch/r56a/deltacheck/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/r56a/deltacheck/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/r56a/deltacheck/unreadable/data/runs)
cand: exit 4
err| mosaic-task: cannot resolve /home/jwoltje/darkwing-scratch/r56a/deltacheck/unreadable/data/runs/r-20260101T000000Z-aaaaaa: EACCES
@@ -0,0 +1,45 @@
✔ isRunId accepts the run id shape and nothing else (1.748725ms)
✔ a missing data root or runs directory lists nothing (2.62682ms)
✔ runs as a regular file lists nothing, as before (0.946792ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.233291ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (3.291242ms)
✔ readRunRecord returns documents and artifacts in directory order (1.713813ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.29214ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.833633ms)
✔ readRunDocument reads only the three run documents (0.879084ms)
✔ a link inside the data root is followed (1.653918ms)
✔ a data root that is itself a link is trusted as configured (1.299716ms)
✔ a run directory linked out of the data root is never read (3.611395ms)
✔ a document linked out of the data root reads as null (4.654578ms)
✔ a runs directory linked out of the data root refuses (1.553974ms)
✔ a relative link that climbs out of the data root refuses (0.913072ms)
✔ a sibling whose name starts with the data root's name is outside it (0.956986ms)
✔ a link loop refuses instead of reading as missing (1.278185ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.756923ms)
✔ an unreadable run directory refuses instead of reading as missing (0.953906ms)
✔ a link to the data root's parent is outside it (0.694287ms)
✔ listRunIds sorts whatever order the directory returns (2.201433ms)
✔ the readers write nothing (3.639856ms)
✔ no pointer is null (2.887848ms)
✔ the pointer release.sh writes reads back (2.51019ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (3.716029ms)
✔ a pointer that is a directory refuses with exit 4 (0.96141ms)
✔ a state file linked out of the data root refuses (2.368395ms)
✔ a state directory linked out of the data root refuses (1.703149ms)
✔ a missing log is empty (0.58268ms)
✔ the log reads oldest first and counts malformed lines (1.176573ms)
✔ last must be a positive integer (0.542446ms)
✔ the state readers write nothing (1.947461ms)
✔ list prints each run in the established format (113.565222ms)
✔ show prints the run in the established format (82.167185ms)
✔ show of a missing run and an invalid id exit 4 as before (136.70629ms)
✔ list and show refuse a runs directory linked out of the data root (178.235466ms)
✔ show refuses a run linked out of the data root; list reports it unknown (191.565769ms)
ℹ tests 37
ℹ suites 0
ℹ pass 37
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 777.874059
@@ -0,0 +1,13 @@
start 2026-10-10T16:55:29Z
node-runs exit=0 ℹ pass 37 ℹ 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=0 discord suite: 66 passed, 0 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-10T16:57:50Z
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 'dc96f87b1776cc74d0ea5ba7701db418cc21e1b8'.
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 'dc96f87b1776cc74d0ea5ba7701db418cc21e1b8'.
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,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: 778025 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/r56a/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
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
# Row 56 probes: edge cases outside Rocko's byte-check and delta-check, base against candidate.
export TMPDIR=~/darkwing-scratch/r56a/tmp
S=~/darkwing-scratch/r56a; W=$S/probes; rm -rf $W; mkdir -p $W
A=r-20260101T000000Z-aaaaaa
RESULT='{"runVersion":1,"taskId":"t","status":"succeeded","request":"r","response":"s","provider":"p","model":"m","startedAt":"a","finishedAt":"b","durationMs":1,"exitCode":0,"signal":null}'
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 mutwt; 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; }
hdr() { printf '\n== %s\n' "$*"; }
hdr "P1 show ../x with no config file (mutwt carries D01 when run by this script)"
run_case p1 "$W/no-such-config.json" show ../x
D="$(root p2)"; mkdir -p "$D/runs/$A"; printf '%s\n' "$RESULT" > "$D/runs/$A/result.json"; chmod 000 "$D/runs/$A"
hdr "P2 show of a run directory with mode 000"; run_case p2 "$W/p2/config.json" show "$A"
chmod 755 "$D/runs/$A"
D="$(root p3)"; mkdir -p "$D/runs/r-.bad" "$D/runs/r-"; printf '%s\n' "$RESULT" > "$D/runs/r-.bad/result.json"
hdr "P3 list with r- names that aren't valid run ids"; run_case p3 "$W/p3/config.json" list
D="$(root p4)"; mkdir -p "$D/runs/$A"; printf '%s\n' "$RESULT" > "$D/runs/$A/result.json"; ln -s . "$D/runs/r-self"; mv "$D/runs" "$D/real-runs"; ln -s real-runs "$D/runs"
hdr "P4 runs/ is a link inside the data root"; run_case p4 "$W/p4/config.json" list
D="$(root p5)"; ln -s "$W/p5/outside" "$D/state"
hdr "P5 state/ linked out of the data root, no active.json or log there"
(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); }
}
});' "$D")
printf '{"pointerVersion":1,"release":"x","imageTag":"y","activatedAt":"z"}\n' > "$W/p5/outside/active.json"
hdr "P5b same, with active.json outside"
(cd $S/cand && node -e '
import("./packages/runs/src/index.mjs").then((m) => {
try { console.log(" readActivePointer: " + JSON.stringify(m.readActivePointer(process.argv[1]))); } catch (e) { console.log(" readActivePointer: " + e.name + " " + e.exitCode + " " + e.message.replace(process.argv[1], "<D>")); }
});' "$D")
D="$(root p6)"; mkdir -p "$D/state"
printf '%s\n' '{"at":"a","event":"activate","release":"0.0.1","imageTag":"t1","from":"0.0.0"}' '{"at":"b","event":"rollback","release":"0.0.0","imageTag":5}' > "$D/state/activation-log.jsonl"
hdr "P6 log lines release.sh rollback would still use (an extra key, a non-string imageTag)"
(cd $S/cand && node -e '
import("./packages/runs/src/index.mjs").then((m) => console.log(" " + JSON.stringify(m.readActivationLog(process.argv[1]))));' "$D")
echo; echo DONE
@@ -0,0 +1,131 @@
# Row 56, runs and releases reader, round 1 review (Darkwing)
Issue #1545, packet comment 27109, request comment 27113, queue revs
343-347. Brief: `docs/plans/2026-10-10_design-implementation.md`, section
"Runs and releases reader module". Base `dc96f87b`. Candidate manifest
sha256 `ed3c5392ae43e1c3541c6bab6a2f25826514124f5f68536e5cc330a39cc5d7a3`,
`build.patch` sha256 `dd7b469b…`. Both match comment 27109, and the
packet manifest checks clean.
Verdict: **approve**. The module reads and never writes, nothing it
returns comes from outside the data root, and `list` and `show` print the
same bytes as before on Rocko's seeded data root. Every suite is green.
Nothing blocks. The notes below are test gaps and README wording, plus
one disagreement with the packet: mutant M31 is not equivalent.
## Method
- I read the whole patch: the five `src/` files, the three test files,
the README, the `mosaic-task.mjs` change, and Rocko's `seed.sh`,
`byte-check.sh` and `delta-check.sh`. I compared the pointer and log
readers against `scripts/release.sh` (`append_log`, the `activate`
printf, `rollback`'s log scan and `status`).
- Detached worktrees at `dc96f87b`: `base` unchanged, `cand` with
`git apply --index build.patch`, then `sha256sum -c` on the manifest,
15 OK. A third worktree, built the same way, for mutants. It checks 15
OK after the runs (`r1/mut/manifest-after.txt`).
- `grep` for any write, mkdir, rename, unlink, chmod, open or symlink call
in `packages/runs/src`: none. The patch touches no Q14-frozen path.
- Rocko's `byte-check.sh` and `delta-check.sh`, base against candidate,
with work directories in my scratch rather than `/tmp`.
- 22 mutants of my own (`r1/mut/mutate.py`, exact text that must match
once), each run against the `packages/runs` suite.
- Six probes for cases the packet doesn't cover (`r1/probes.sh`, output
in `r1/mut/probes.txt`), run against base, candidate and a candidate
carrying mutant D01.
Node v26.8.1, `TMPDIR=~/darkwing-scratch/r56a/tmp`. `gate.sh` with
`DOCKER_HOST=unix:///nonexistent.sock` ran 16:55:29Z to 16:57:50Z.
Mutants ran 16:57:19Z to 16:57:40Z in the other worktree, then probes,
then `test-release` with Docker, which finished at 16:58:33Z.
## Suites
| Suite | Result |
|---|---|
| packages/runs (node) | 37/0 |
| test-auth | 15/0 |
| test-conductor | 17/0 |
| test-config | 24/0 |
| test-discord | 66/0 |
| test-extension-package | 18/0 |
| test-foundation | 44/0 |
| test-queue | 27/0 |
| test-release | 4/0 without Docker, 14/0 with it (`test-release-docker.txt`) |
| test-task, Docker unreachable | 26/0, four skip lines |
| byte-check.sh | 16 cases identical, data root unchanged |
| delta-check.sh | same output as Rocko's apart from the scratch path in one stack trace |
I didn't run test-task with real Docker, because it makes live model
calls. Rocko's gate ran it at 98/0, and the change to `mosaic-task.mjs` is
limited to `list` and `show`, which the package's CLI tests and the byte
check cover.
## Mutants
17 of 22 killed (`r1/mut/summary.txt`). The five survivors:
| Mutant | Change | Observable? |
|---|---|---|
| D01 | the CLI's `isRunId` check before `loadConfig` removed (Rocko's M31) | yes, probe P1 |
| D10 | `isInside` treats a path equal to the data root as outside | only when a link resolves to the data root itself |
| D17 | `listRunRecords` reads through `readRunDocument`, which checks the id | yes, probe P3: `list` exits 4 on a run named `r-` |
| D20 | `listRunIds` keeps `isRunId` names only | yes, probe P3: `list` hides `r-` and `r-.bad` |
| D22 | a log entry without `imageTag` accepted | yes, by reading the log |
## Probes
| Probe | Base | Candidate |
|---|---|---|
| P1 `show ../x` with no config file | exit 4, `invalid run id` | the same. With D01: exit 3, `configuration problem` |
| P2 `show` of a run directory with mode 000 | prints two lines, then a stack trace, exit 1 | `cannot read ...: EACCES`, exit 4 |
| P3 `list` with runs named `r-` and `r-.bad` | lists both | the same |
| P4 `runs/` a link inside the data root, plus a run linked to `.` | lists both | the same |
| P5 `state/` linked out, no files there | not in base | pointer `null`, log empty. With `active.json` there: refuses, exit 4 |
| P6 log lines with an extra key, and a numeric `imageTag` | not in base | both counted in `malformed` |
## Notes (not blocking)
1. M31 isn't equivalent. The `isRunId` check in `showRun` is the only id
check that runs before `loadConfig`, so it decides the exit code and
message when the config is bad (P1: 4 against 3). The candidate keeps
the base order, so nothing regresses, but a test of `show ../x` with
`MOSAIC_CONFIG` pointing at a missing file would pin it. I agree that
M04 can't be reached on POSIX.
2. No test lists a run whose name starts with `r-` but isn't a valid id,
so D17 and D20 survive. `runs.mjs` says such names are still listed,
and base does list them. One `listRunRecords` case with `r-` or
`r-.bad` would kill both.
3. `logEntry` accepts an entry with no `imageTag` (D22). The malformed
cases test a wrong type, a null `note` and an extra key, but not a
missing key.
4. README wording, three places:
- "`runs/` or `state/` resolving outside refuses" holds for `runs/`.
For `state/`, the module resolves only the file, so a `state/` link
out with no files behind it reads as no release and an empty log
(P5). Nothing outside is read, so the invariant holds and only the
sentence is wrong.
- "the way `release.sh rollback` skips them": rollback skips only lines
that don't parse. P6's two lines would still be used by rollback and
are counted malformed here. `append_log` writes only the five keys,
so real logs don't hit this. The stricter reader is fine. The
comparison in the README and in `state.mjs` isn't accurate.
- The delta table has no row for P2. `show` of a run directory it
can't read used to crash after printing two lines. It now refuses
with exit 4. That's an improvement and the package test covers it,
but `delta-check.sh` doesn't record it.
5. D10 needs no test. It matters only for a link that resolves to the
data root itself, which is inside by any reading.
6. The `padEnd` crash in `list` that Rocko reported is still there and
still predates this row. Sage files it as a follow-up.
## Files
- `r1/candidate-manifest.sha256`: copy of Rocko's.
- `r1/gate.sh`, `r1/out/`: suite runs, `summary.txt`, byte and delta
checks, test-release with Docker.
- `r1/mut/`: mutant definitions, runner, diffs, outputs, `summary.txt`
and the manifest check after the runs.
- `r1/probes.sh`, `r1/mut/probes.txt`: the six probes. In that run the
third tree (`mutwt`) carries D01, and the D17 and D20 runs of P3 follow
at the end.