review(56): Rocko round 1 and round 2 build packet (packet manifest 5eb2706d)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-10 12:22:54 -05:00
co-authored by Claude Opus 5.5
parent 71d874764a
commit 8dff857f64
39 changed files with 4259 additions and 0 deletions
+258
View File
@@ -0,0 +1,258 @@
# Row 56 (#1545): runs and releases reader module, candidate packet, round 2
Author: Rocko. Reviewers: Darkwing and Filbert. Brief:
`docs/plans/2026-10-10_design-implementation.md`, section "Runs and releases
reader module". Base: `75ab1646` (`base.txt`), the origin/refactor Sage
pushed with the round 1 review records. Round 1 was candidate `ed3c5392`
over `dc96f87b`. No file in this candidate changed upstream between the two
bases. The packet is uncommitted. I made no commits, pushes or Gitea calls,
and read no token or private binding.
The round 2 section comes first. The round 1 text follows and still holds
except where round 2 corrects it; the corrections are marked in place.
## Round 2 changes (against ed3c5392)
Round 1 was approved by Darkwing (#1545 comment 27115, rev 348) and
Filbert (comment 27116). Sage ruled round 2 in: tests and wording only.
No `src/` logic changes. `scripts/mosaic-task.mjs` is byte-identical to
round 1. `out/round2-delta.diff` is the exact diff of the six files that
changed:
| File | Change | Answers |
|---|---|---|
| `packages/runs/tests/task-cli.test.mjs` | +2 tests: `show ../x` with a missing config exits 4 "invalid run id", not 3; `list` shows `r-.bad` as before | Darkwing 1 (M31), Darkwing 2 |
| `packages/runs/tests/runs.test.mjs` | +1 test: `listRunIds` and `listRunRecords` on `r-.bad` give `["r-.bad"]` and `[{ runId: "r-.bad", result: {} }]`, the same as base | Darkwing 2 (D17, D20) |
| `packages/runs/tests/state.test.mjs` | malformed log lines gain an entry with no `imageTag` and `{ ...good[1], release: 5 }`, so malformed goes 5 → 7; +1 test: a `state/` link out to nothing reads as no release and an empty log | Darkwing 3 (D22), Filbert N2, Darkwing 4a |
| `packages/runs/src/state.mjs` | comment above `readActivationLog` only: says what is skipped and that rollback skips only lines that aren't JSON | Filbert N1, Darkwing 4b |
| `packages/runs/README.md` | links with nothing behind them read as missing, `state/` included; the activation log bullet says what the module skips against rollback; a delta row for `show` on a mode-000 run directory | Darkwing 4a, 4b, 4c; Filbert N1 |
| `agents/rocko/work/queue-56/delta-check.sh` | + the case "run directory unreadable: show" | Darkwing 4c |
Rollback's actual rule, from `scripts/release.sh` lines 91–99: it drops
empty lines, skips lines that `JSON.parse` rejects, and acts on any parsed
`activate` or `rollback` entry with a truthy `imageTag`. The module keeps
its strict entry shape, so a line rollback would use can count as
malformed here.
### Round 2 evidence (scratch worktree of the round 2 candidate at 75ab1646)
- packages/runs: 41/41 (`out/runs-tests.log`).
- test-task: 98 passed, 0 failed, with Docker (`out/gate/test-task.log`).
The other suites aren't rerun, because round 2 changes only package
tests, comments, docs and the check script; round 1's results for them
stand.
- byte-check against a base worktree at 75ab1646: 16 cases, stdout,
stderr and exit identical; data root unchanged (`out/byte-check.txt`).
- delta-check: 13 cases now (`out/delta-check.txt`). The new one:
base `show` printed `run:` and `result.json: (missing or unreadable)`
and then crashed in `readdirSync`, exit 1; the candidate refuses with
`cannot read …: EACCES`, exit 4.
- mutants: 37 (`out/mutants.sh`, `out/mutants.txt`). Round 1's 32, plus
Darkwing's D17, D20 and D22, Filbert's N2, and S1 (the dangling `state/`
link reading as missing). 36 are killed. Only M04 survives, and it is
equivalent (below). Each new kill comes from an assertion in the new
tests, not a syntax or reference error.
- `build.patch`: `git diff --cached --binary 75ab1646`, 15 files,
+1068/−27. It applies to 75ab1646 and the manifest matches
(`out/apply-75ab1646.txt`).
### Correction: M31 was never equivalent
Round 1 called M31 equivalent: "`readRunRecord` throws the same message".
That is wrong. The `isRunId` check in `showRun` is the only id check that
runs before `loadConfig`. Without it, `show ../x` with a missing config
exits 3 with a configuration problem instead of 4 "invalid run id"
(Darkwing's probe P1). The round 1 paragraph even said the check stays "so
that an invalid id refuses before the config loads", which contradicted
its own verdict. No test covered that case. The new CLI test does, and
M31 is killed.
# Round 1 (candidate ed3c5392 over dc96f87b)
Round 1 evidence is under `out/round1/`. Paths below that start `out/` mean `out/round1/` in this packet.
The round 1 packet files are in `r1/`, unchanged, so the posted hashes still check:
`r1/build.patch` dd7b469b…, `r1/candidate-manifest.sha256` ed3c5392… and
`r1/packet-manifest.sha256` e0497ad1…. That manifest lists the round 1
paths, so it checks against the round 1 tree, not this directory.
## Files (`files.txt`, 15)
| File | Change |
|---|---|
| `packages/runs/package.json` | new: `@mosaic/runs`, private, no dependencies |
| `packages/runs/src/errors.mjs` | new: `RunsError(message, exitCode = 4)` |
| `packages/runs/src/paths.mjs` | new: `resolveInside` containment, `readJsonObject` |
| `packages/runs/src/runs.mjs` | new: `isRunId`, `listRunIds`, `listRunRecords`, `readRunDocument`, `readRunRecord` |
| `packages/runs/src/state.mjs` | new: `readActivePointer`, `readActivationLog` |
| `packages/runs/src/index.mjs` | new: re-exports |
| `packages/runs/README.md` | new: what it reads, the limits, the deliberate deltas |
| `packages/runs/tests/*.mjs` | new: 37 tests in round 1 (runs 22, state 10, task-cli 5) plus helpers; 41 in round 2 (runs 23, state 11, task-cli 7) |
| `scripts/mosaic-task.mjs` | `list` and `show` read through the module, +18/−27 |
| `agents/rocko/work/queue-56/{seed,byte-check,delta-check}.sh` | the byte-identity and delta checks |
`build.patch` is `git diff --cached --binary dc96f87b` over those files:
+1021/−27 in round 1. Round 2's `build.patch` is over 75ab1646, +1068/−27. `candidate-manifest.sha256` hashes the 15 files.
Q14 freeze: nothing under `packages/bus`, `packages/tasks`, `packages/cli`,
`scripts/bus-service.sh` or `scripts/mosaic` changes. Apart from the import,
`scripts/mosaic-task.mjs` is the only file under `scripts/` that changes. The
module covers the runs view's needs, but the view itself, `release.sh` and
pruning are untouched.
## Design
- **Containment.** `resolveInside` runs `realpathSync` on the data root and
the target, then applies a `path.relative` check:
- `..`, `../…` or an absolute relative path counts as outside;
- the configured data root is trusted even when it is itself a link;
- links that stay inside it are followed.
- **Outside paths.**
- `runs/`, `state/`, a run directory read by `readRunRecord`, `active.json`
and the activation log refuse when they resolve outside.
- A run document that resolves outside reads as `null`, the way an
unreadable one always has.
- **Missing versus broken.** ENOENT and ENOTDIR read as missing. Anything
else (ELOOP, EACCES) refuses with the error code instead of reading as
empty.
- **Documents.** A run document is a JSON object or `null`. Run records are
not validated further, because older records carry older shapes.
- **Release pointer.** `active.json` is strict: exactly the version 1 shape
`release.sh` writes. Bad data exits 2, and a read error exits 4.
- **Activation log.** The log is lenient per line, like `release.sh
rollback`. It returns `{ entries, malformed }`, and `last` keeps the newest
entries.
**Corrected in round 2:** not "like rollback". Rollback skips only
lines that aren't JSON; this module also counts wrong-shaped entries.
- **Errors.** Every refusal is a `RunsError` with exit code 2 or 4.
`mosaic-task.mjs` maps it to `fail(exitCode, message)` through
`readRuns`.
- **Read-only.** No function writes. The "readers write nothing" test and
byte-check's before/after snapshot of the data root both check this.
## Gate (`out/gate/`, sequential, scratch worktree of the candidate)
| Suite | Result |
|---|---|
| packages/runs tests | 34/34 at gate time; 37/37 after the mutant round (`out/runs-tests-final.log`) |
| test-auth | 15 passed, 0 failed |
| test-conductor | 17 passed, 0 failed |
| test-config | 24 passed, 0 failed |
| test-discord | first run 57 passed, 1 failed; rerun 66 passed, 0 failed (below) |
| test-extension-package | 18 passed, 0 failed |
| test-foundation | 44 passed, 0 failed |
| test-queue | 27 passed, 0 failed |
| test-release | 14 passed, 0 failed |
| test-task | 98 passed, 0 failed |
**test-discord's first-run failure was environmental.** The failing case was
"pi binary present at node_modules/.bin/pi for the extension checks". The
scratch worktree has no `node_modules`, and the base worktree lacks it too.
I symlinked the checkout's `node_modules` into the scratch tree and reran
the suite: 66 passed, 0 failed. The extra 8 cases are the extension checks
that the missing binary had skipped. Then I removed the symlink
(`out/gate/test-discord-r2.log`). Nothing in this candidate touches
packages/discord. Sage's rerun in a tree that has `node_modules` should see
66.
After the gate, the only changes were tests, the README table row and the
check scripts. No `src/` or `scripts/mosaic-task.mjs` change came after it.
## Byte identity (`out/byte-check.txt`)
`byte-check.sh BASE CAND WORK` seeds a data root with `seed.sh`. The seed
holds:
- runs a–f;
- a run that is a file;
- an internal link and a dangling link;
- `r-zz.weird_name-1`;
- a non-`r-` name, `.pruned.log` and a stray file.
It runs 16 cases from both trees: `list` on seeded, empty and absent roots,
and `show` on each run, a missing run, `../escape`, no argument and an
absent root. It diffs stdout, stderr and exit codes.
```
byte-check: 16 cases, stdout, stderr and exit identical
byte-check: data root unchanged
```
**Correction to my own harness.** My first rerun in this round passed a
relative WORK_DIR. Every redirect inside the `cd` subshell failed, both trees
recorded exit 1, and the diff still reported "identical": a false pass.
Earlier runs used absolute paths and were not affected. Both scripts now
make WORK_DIR absolute before doing anything else. `byte-check.sh` also
exits 4 if any case leaves no stdout or stderr file. The output above
comes from the fixed script.
## Deliberate deltas (`out/delta-check.txt`)
These cases fall outside the byte-identity domain: a link out of the data
root, a document that is JSON but not an object, a run that is a file, a
link loop, and an unreadable directory. Each one changes on purpose. The
README table lists them, and `delta-check.sh` prints base against candidate:
- run directory linked out: `list` shows `unknown`; `show` refuses, exit 4.
- `result.json` linked out: `list` shows `unknown`; `show` prints
`(missing or unreadable)`.
- `runs/` linked out: `list` and `show` refuse, exit 4.
- `result.json` is `5`: base `list` crashed (exit 1); base `show` printed
`undefined` fields. The candidate gives `unknown` and
`(missing or unreadable)`.
- `task.json` is `[]`: base printed the snapshot line; the candidate
omits it.
- run is a regular file: base `show` crashed in `readdirSync`; the
candidate gives `run not found`, exit 4.
- link loop: base said `run not found`; the candidate refuses with ELOOP,
exit 4.
- `runs/` unreadable: base `list` printed nothing, exit 0; the candidate
refuses with EACCES, exit 4.
## Mutants (`out/mutants.sh`, `out/mutants.txt`)
I ran 32 single-line mutants of `src/` and `scripts/mosaic-task.mjs`
against the packages/runs tests.
First round: 26 killed, 4 survived and 2 didn't apply. Three of the
survivors were real gaps, and I added a test for each:
- **M02** dropped the exact `..` check. A link to the data root's parent got
through. New test: "a link to the data root's parent is outside it".
- **M09** dropped the sort. Node returned this directory already sorted,
so no fixture could catch it. New test: it mocks `fs.readdirSync` to
return the names reversed.
- **M15** read every readdir error on a run directory as missing. New
test: "an unreadable run directory refuses instead of reading as
missing".
Final round: 30 killed, 2 survived. Both survivors are equivalent:
- **M04** dropped `!path.isAbsolute(relative)`. On POSIX, `path.relative`
between two absolute paths is never absolute, so the branch is
unreachable on Linux. I kept it as a guard for other platforms.
- **M31** dropped the CLI's `isRunId` pre-check in `show`. `readRunRecord`
throws the same message with exit 4, and `readRuns` maps it to the same
`fail`. The pre-check stays so that an invalid id refuses before the
config loads, as it did before.
**Corrected in round 2:** M31 was not equivalent. See "Correction: M31
was never equivalent" above; it is killed in round 2.
## Follow-up (not fixed, out of scope)
This defect predates the candidate and is unchanged: `list` crashes with
a `padEnd` TypeError when `result.json` is an object without a string
`status` or `taskId`. Base and candidate behave the same here, so
byte-identity holds. A fix would change `list` output and belongs in its
own row.
## Reproduce
```sh
git worktree add --detach /tmp/r56-base 75ab1646
git worktree add --detach /tmp/r56-cand 75ab1646
(cd /tmp/r56-cand && git apply --index <packet>/build.patch && sha256sum -c <packet>/candidate-manifest.sha256)
env -u NODE_TEST_CONTEXT node --test '/tmp/r56-cand/packages/runs/tests/*.test.mjs'
bash /tmp/r56-cand/agents/rocko/work/queue-56/byte-check.sh /tmp/r56-base /tmp/r56-cand /tmp/r56-bc
bash /tmp/r56-cand/agents/rocko/work/queue-56/delta-check.sh /tmp/r56-base /tmp/r56-cand /tmp/r56-dc
```
+1
View File
@@ -0,0 +1 @@
75ab164698176178f196009f14f994e0192048a7
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
agents/rocko/work/queue-56/byte-check.sh
agents/rocko/work/queue-56/delta-check.sh
agents/rocko/work/queue-56/seed.sh
packages/runs/package.json
packages/runs/README.md
packages/runs/src/errors.mjs
packages/runs/src/index.mjs
packages/runs/src/paths.mjs
packages/runs/src/runs.mjs
packages/runs/src/state.mjs
packages/runs/tests/helpers.mjs
packages/runs/tests/runs.test.mjs
packages/runs/tests/state.test.mjs
packages/runs/tests/task-cli.test.mjs
scripts/mosaic-task.mjs
@@ -0,0 +1 @@
applies to 75ab1646, manifest matches
@@ -0,0 +1,3 @@
byte-check: 16 cases, stdout, stderr and exit identical
byte-check: data root unchanged
rc=0
@@ -0,0 +1,119 @@
== 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: /mnt/storage/scratch/rocko-r56/dc3/run-link/data/runs/r-20260101T000000Z-aaaaaa resolves outside the data root (/mnt/storage/scratch/rocko-r56/dc3/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: /mnt/storage/scratch/rocko-r56/dc3/runs-link/data/runs resolves outside the data root (/mnt/storage/scratch/rocko-r56/dc3/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: /mnt/storage/scratch/rocko-r56/dc3/runs-link/data/runs resolves outside the data root (/mnt/storage/scratch/rocko-r56/dc3/runs-link/data)
== result.json is 5 and task.json is []: list
base: exit 1
err| file:///mnt/storage/scratch/rocko-r56/base2/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 /mnt/storage/scratch/rocko-r56/dc3/run-file/data/runs)
== run is a link loop: show
base: exit 4
err| mosaic-task: run not found: r-20260101T000000Z-aaaaaa (under /mnt/storage/scratch/rocko-r56/dc3/loop/data/runs)
cand: exit 4
err| mosaic-task: cannot resolve /mnt/storage/scratch/rocko-r56/dc3/loop/data/runs/r-20260101T000000Z-aaaaaa: ELOOP
== runs directory unreadable: list
base: exit 0
cand: exit 4
err| mosaic-task: cannot read /mnt/storage/scratch/rocko-r56/dc3/unreadable/data/runs: EACCES
== runs directory unreadable: show
base: exit 4
err| mosaic-task: run not found: r-20260101T000000Z-aaaaaa (under /mnt/storage/scratch/rocko-r56/dc3/unreadable/data/runs)
cand: exit 4
err| mosaic-task: cannot resolve /mnt/storage/scratch/rocko-r56/dc3/unreadable/data/runs/r-20260101T000000Z-aaaaaa: EACCES
== run directory unreadable: show
base: exit 1
out| run: r-20260101T000000Z-aaaaaa
out| result.json: (missing or unreadable)
err| node:fs:1954
cand: exit 4
err| mosaic-task: cannot read /mnt/storage/scratch/rocko-r56/dc3/run-unreadable/data/runs/r-20260101T000000Z-aaaaaa: EACCES
rc=0
@@ -0,0 +1,102 @@
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)
OK mock adapter: gate passes on matching mock response (exit 0)
OK mock adapter: expect-mismatch recorded (exit 1)
OK mismatch reason recorded
OK unknown adapter fails closed (exit 1)
OK mission task runs via mock adapter (exit 0)
OK mission section injected into generated prompt
OK retry of mission run succeeds (exit 0)
OK retriedFrom lineage recorded
OK mission section present after retry (relative path resolved)
OK retry of missing run exits 4 (exit 4)
OK skill install bundled ms-tools (exit 0)
OK installed to skills-available
OK double install refuses (exit 1)
OK activate enables skill (exit 0)
OK enabled dir populated
OK uninstall while enabled refuses (exit 1)
OK deactivate moves back to available (exit 0)
OK uninstall removes from available (exit 0)
OK seat with enabled skill launches
OK skill path delivered to adapter
OK shipped researcher contract resolves (ceiling + network)
OK resolve-role refuses a non-role document (conductor policy) (exit 2)
OK resolve-role refuses name/filename mismatch (exit 2)
OK resolve-role refuses unknown network declaration (exit 2)
OK resolve-role refuses duplicate role tool (exit 2)
OK resolve-role refuses unsupported tool (exit 2)
OK resolve-role refuses missing contract file (exit 4)
OK version 1 role prints no contract line
OK shipped version 2 roles resolve with their contracts (pm, cto, coder, reviewer)
OK resolve-role accepts a minimal version 2 role (exit 0)
OK resolve-role refuses an action outside the vocabulary (exit 2)
OK resolve-role refuses a gated-only action in a role file (exit 2)
OK resolve-role refuses a Vikunja verb no role may hold (exit 2)
OK resolve-role refuses a version 2 role whose contract is missing (exit 2)
OK resolve-role refuses a contract that is a symbolic link (exit 2)
OK seat launches under role ceiling
OK role ceiling narrows seat tools (read,write,bash -> read,bash)
OK narrowing recorded loudly
OK missing role contract refuses launch (exit 2)
OK missing-contract refusal names the role
OK empty ceiling intersection -> tool-free seat
OK tool-free outcome recorded loudly
OK overridden agents dir without seat definition refuses (exit 4)
OK seat-resolution refusal names the seat
OK policy: mission only -> mission tools
OK policy: task only -> task tools
OK policy: both -> intersection (task narrowed)
OK policy: empty intersection -> tool-free
OK invalid mission capabilities rejected (exit 2)
mosaic-task: unsupported mission tool: "sudo" (supported: read, write, edit, bash, grep, find, ls)
OK task run with user layer present (exit 0)
OK user context dispatched into generated prompt
OK workspace+tools task runs via mock (exit 0)
OK workspace path + tools delivered to adapter
OK persistent workspace created on host
OK plain task still runs (no workspace/tools) (exit 0)
OK workspace var present but empty when absent
OK tools empty when absent
OK unknown tool exits 2 (exit 2)
OK workspace traversal exits 2 (exit 2)
OK live hello task succeeds with exact marker
OK result.json written in run dir
OK result.json contents are correct
OK wrong expectExact fails with exit 1 (reason: expect-mismatch)
OK each run gets a distinct run dir (no clobber)
OK list shows both runs
OK fork base: teach succeeds (exit 0)
OK fork child recalls ancestor context (exit 0)
OK forked child recalled ancestor code word
OK ancestor session untouched by fork
OK child session dir has its own branch file
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
OK user recall run succeeds
OK recalled user name (response: Jason)
OK no agent identity on headless run
selftest: 98 passed, 0 failed
rc=0
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Row 56 mutants: each line applies one perl substitution to one file, runs
# the packages/runs tests, and expects a failure (killed). Restores after.
cd /mnt/storage/scratch/rocko-r56/r2 || exit 4
G=/mnt/storage/scratch/rocko-r56/gate2
: > "$G/mutants.txt"
mut() { # id file perl-expr
local id="$1" f="$2" e="$3"
cp "$f" "$f.orig"
perl -0pi -e "$e" "$f"
if cmp -s "$f" "$f.orig"; then echo "$id NOT-APPLIED" >> "$G/mutants.txt"; mv -f "$f.orig" "$f"; return; fi
if timeout 300 env -u NODE_TEST_CONTEXT node --test 'packages/runs/tests/*.test.mjs' > "$G/mut-$id.log" 2>&1; then
echo "$id SURVIVED" >> "$G/mutants.txt"
else
echo "$id killed" >> "$G/mutants.txt"
fi
mv -f "$f.orig" "$f"
}
P=packages/runs/src/paths.mjs; R=packages/runs/src/runs.mjs; S=packages/runs/src/state.mjs; T=scripts/mosaic-task.mjs
mut M01 $P 's/return relative === "" \|\|/return true ||/'
mut M02 $P 's/relative !== "\.\." && //'
mut M03 $P 's/startsWith\(`\.\.\$\{path\.sep\}`\)/startsWith("..x")/'
mut M04 $P 's/ && !path\.isAbsolute\(relative\)\)/)/'
mut M05 $P 's/ && !Array\.isArray\(value\) \? value/ ? value/'
mut M06 $P 's/error\?\.code === "ENOENT" \|\| error\?\.code === "ENOTDIR"/error?.code === "ENOENT"/'
mut M07 $P 's/if \(isMissing\(error\)\) return null;\n throw new RunsError\(`cannot resolve/return null;\n throw new RunsError(`cannot resolve/'
mut M08 $P 's/if \(!isInside\(root, real\)\)/if (false)/'
mut M09 $R 's/filter\(\(name\) => name\.startsWith\("r-"\)\)\.sort\(\)/filter((name) => name.startsWith("r-"))/'
mut M10 $R 's/filter\(\(name\) => name\.startsWith\("r-"\)\)/filter(() => true)/'
mut M11 $R 's/if \(isMissing\(error\)\) return \[\];\n throw/return [];\n throw/'
mut M12 $R 's/if \(!RUN_DOCUMENTS\.includes\(name\)\) throw/if (false) throw/'
mut M13 $R 's/export function readRunRecord\(dataRoot, runId\) \{\n requireRunId\(runId\);/export function readRunRecord(dataRoot, runId) {/'
mut M14 $R 's/if \(resolveInside\(dataRoot, RUNS_DIRNAME\) === null\) return null;\n//'
mut M15 $R 's/if \(isMissing\(error\)\) return null;\n throw new RunsError\(`cannot read \$\{dir\}/return null;\n throw new RunsError(`cannot read ${dir}/'
mut M16 $R 's/RUN_ID_PATTERN = \/\^r-/RUN_ID_PATTERN = \/r-/'
mut M17 $S 's/if \(!POINTER_KEYS\.includes\(key\)\) throw/if (false) throw/'
mut M18 $S 's/if \(pointer\.pointerVersion !== 1\)/if (false)/'
mut M19 $S 's/new RunsError\(`release pointer \$\{why\} \(\$\{state\.file\}\)`, 2\)/new RunsError(`release pointer ${why} (${state.file})`)/'
mut M20 $S 's/if \(Object\.keys\(entry\)\.some\(\(key\) => !LOG_KEYS\.includes\(key\)\)\) return null;//'
mut M21 $S 's/if \(entry\.note !== undefined && typeof entry\.note !== "string"\) return null;//'
mut M22 $S 's/entries\.slice\(-last\)/entries.slice(0, last)/'
mut M23 $S 's/if \(line\.trim\(\) === ""\) continue;//'
mut M24 $S 's/if \(last !== undefined && \(!Number\.isInteger\(last\) \|\| last < 1\)\)/if (false)/'
mut M25 $S 's/ throw new RunsError\(`cannot read \$\{file\}/ return null; throw new RunsError(`cannot read ${file}/'
mut M26 $S 's/if \(!isNonEmptyString\(pointer\[key\]\)\)/if (pointer[key] === undefined)/'
mut M27 $S 's/\["at", "event", "release", "imageTag"\]\.every/["event"].every/'
mut M28 $T 's/ if \(result\) \{\n status = result\.status;/ if (true) {\n status = result.status;/'
mut M29 $T 's/ if \(record === null\) \{\n fail\(4, `run not found/ if (false) {\n fail(4, `run not found/'
mut M30 $T 's/if \(error instanceof RunsError\) fail\(error\.exitCode, error\.message\);/if (false) fail(error.exitCode, error.message);/'
mut M31 $T 's/ if \(!isRunId\(runId\)\) \{\n fail\(4, `invalid run id/ if (false) {\n fail(4, `invalid run id/'
mut M32 $T 's/artifacts\.join\(", "\)/artifacts.join(",")/'
mut D17 $R 's/result: readJsonObject\(dataRoot, RUNS_DIRNAME, runId, "result\.json"\)/result: readRunDocument(dataRoot, runId, "result.json")/'
mut D20 $R 's/names\.filter\(\(name\) => name\.startsWith\("r-"\)\)/names.filter((name) => name.startsWith("r-") \&\& isRunId(name))/'
mut D22 $S 's/\["at", "event", "release", "imageTag"\]\.every/["at", "event", "release"].every/'
mut N2 $S 's/\["at", "event", "release", "imageTag"\]\.every/["at", "event", "imageTag"].every/'
mut S1 $S 's/if \(file === null\) return null;\n try \{\n return \{ file, text/if (file === null) throw new RunsError("x");\n try {\n return { file, text/'
echo done >> "$G/mutants.txt"
@@ -0,0 +1,38 @@
M01 killed
M02 killed
M03 killed
M04 SURVIVED
M05 killed
M06 killed
M07 killed
M08 killed
M09 killed
M10 killed
M11 killed
M12 killed
M13 killed
M14 killed
M15 killed
M16 killed
M17 killed
M18 killed
M19 killed
M20 killed
M21 killed
M22 killed
M23 killed
M24 killed
M25 killed
M26 killed
M27 killed
M28 killed
M29 killed
M30 killed
M31 killed
M32 killed
D17 killed
D20 killed
D22 killed
N2 killed
S1 killed
done
@@ -0,0 +1 @@
applies to 923957e2, manifest matches
@@ -0,0 +1 @@
applies to dc96f87b, manifest matches
@@ -0,0 +1,3 @@
byte-check: 16 cases, stdout, stderr and exit identical
byte-check: data root unchanged
rc=0
@@ -0,0 +1,111 @@
== 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: /mnt/storage/scratch/rocko-r56/dc2/run-link/data/runs/r-20260101T000000Z-aaaaaa resolves outside the data root (/mnt/storage/scratch/rocko-r56/dc2/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: /mnt/storage/scratch/rocko-r56/dc2/runs-link/data/runs resolves outside the data root (/mnt/storage/scratch/rocko-r56/dc2/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: /mnt/storage/scratch/rocko-r56/dc2/runs-link/data/runs resolves outside the data root (/mnt/storage/scratch/rocko-r56/dc2/runs-link/data)
== result.json is 5 and task.json is []: list
base: exit 1
err| file:///mnt/storage/scratch/rocko-r56/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 /mnt/storage/scratch/rocko-r56/dc2/run-file/data/runs)
== run is a link loop: show
base: exit 4
err| mosaic-task: run not found: r-20260101T000000Z-aaaaaa (under /mnt/storage/scratch/rocko-r56/dc2/loop/data/runs)
cand: exit 4
err| mosaic-task: cannot resolve /mnt/storage/scratch/rocko-r56/dc2/loop/data/runs/r-20260101T000000Z-aaaaaa: ELOOP
== runs directory unreadable: list
base: exit 0
cand: exit 4
err| mosaic-task: cannot read /mnt/storage/scratch/rocko-r56/dc2/unreadable/data/runs: EACCES
== runs directory unreadable: show
base: exit 4
err| mosaic-task: run not found: r-20260101T000000Z-aaaaaa (under /mnt/storage/scratch/rocko-r56/dc2/unreadable/data/runs)
cand: exit 4
err| mosaic-task: cannot resolve /mnt/storage/scratch/rocko-r56/dc2/unreadable/data/runs/r-20260101T000000Z-aaaaaa: EACCES
rc=0
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Row 56 gate: packages/runs tests then every scripts/test-*.sh, sequentially.
cd /mnt/storage/scratch/rocko-r56/tree || exit 4
G=/mnt/storage/scratch/rocko-r56/gate
: > "$G/summary.txt"
env -u NODE_TEST_CONTEXT node --test 'packages/runs/tests/*.test.mjs' > "$G/runs.log" 2>&1
echo "packages/runs rc=$? $(grep -E '^ℹ (pass|fail) ' "$G/runs.log" | tr '\n' ' ')" >> "$G/summary.txt"
for s in scripts/test-*.sh; do
n="$(basename "$s" .sh)"
env -u NODE_TEST_CONTEXT bash "$s" > "$G/$n.log" 2>&1
echo "$n rc=$? $(grep -aiE 'pass(ed)?[:=]? *[0-9]+|[0-9]+ *(passed|failed)|PASS|FAIL' "$G/$n.log" | tail -1)" >> "$G/summary.txt"
done
echo done >> "$G/summary.txt"
@@ -0,0 +1,42 @@
✔ isRunId accepts the run id shape and nothing else (1.679687ms)
✔ a missing data root or runs directory lists nothing (1.711645ms)
✔ runs as a regular file lists nothing, as before (0.711141ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.883213ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.444986ms)
✔ readRunRecord returns documents and artifacts in directory order (1.167168ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.70133ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.793561ms)
✔ readRunDocument reads only the three run documents (0.730363ms)
✔ a link inside the data root is followed (1.147751ms)
✔ a data root that is itself a link is trusted as configured (0.641352ms)
✔ a run directory linked out of the data root is never read (0.659011ms)
✔ a document linked out of the data root reads as null (1.093445ms)
✔ a runs directory linked out of the data root refuses (0.615519ms)
✔ a relative link that climbs out of the data root refuses (0.563326ms)
✔ a sibling whose name starts with the data root's name is outside it (0.703948ms)
✔ a link loop refuses instead of reading as missing (0.914245ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.587674ms)
✔ the readers write nothing (2.658199ms)
✔ no pointer is null (2.102149ms)
✔ the pointer release.sh writes reads back (1.402897ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (2.240364ms)
✔ a pointer that is a directory refuses with exit 4 (0.676911ms)
✔ a state file linked out of the data root refuses (2.216034ms)
✔ a state directory linked out of the data root refuses (0.96548ms)
✔ a missing log is empty (0.663032ms)
✔ the log reads oldest first and counts malformed lines (1.490356ms)
✔ last must be a positive integer (0.835225ms)
✔ the state readers write nothing (1.481171ms)
✔ list prints each run in the established format (94.815455ms)
✔ show prints the run in the established format (81.715499ms)
✔ show of a missing run and an invalid id exit 4 as before (145.886523ms)
✔ list and show refuse a runs directory linked out of the data root (171.425218ms)
✔ show refuses a run linked out of the data root; list reports it unknown (196.821506ms)
ℹ tests 34
ℹ suites 0
ℹ pass 34
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 774.958121
@@ -0,0 +1,11 @@
packages/runs rc=0 ℹ pass 34 ℹ fail 0
test-auth rc=0 selftest: 15 passed, 0 failed
test-conductor rc=0 selftest: 17 passed, 0 failed
test-config rc=0 selftest: 24 passed, 0 failed
test-discord rc=1 discord suite: 57 passed, 1 failed
test-extension-package rc=0 extension package selftest: 18 passed, 0 failed
test-foundation rc=0 selftest: 44 passed, 0 failed
test-queue rc=0 queue suite: 27 passed, 0 failed
test-release rc=0 selftest: 14 passed, 0 failed
test-task rc=0 selftest: 98 passed, 0 failed
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,62 @@
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
FAIL pi binary present at node_modules/.bin/pi for the extension checks
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: 57 passed, 1 failed
@@ -0,0 +1,21 @@
OK initial ordinary-file install
OK installed tree matches canonical source
OK installed tree has no symlinks
OK check detects installation drift
OK sync refuses to overwrite installation drift
OK check detects an extra destination file
OK check detects an extra destination directory
OK check rejects a destination symlink
OK sync accepts a canonical source update
OK updated installation matches canonical source
scripts/test-extension-package.sh: line 14: 561594 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 (/mnt/storage/scratch/rocko-r56/tree) 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,101 @@
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)
OK mock adapter: gate passes on matching mock response (exit 0)
OK mock adapter: expect-mismatch recorded (exit 1)
OK mismatch reason recorded
OK unknown adapter fails closed (exit 1)
OK mission task runs via mock adapter (exit 0)
OK mission section injected into generated prompt
OK retry of mission run succeeds (exit 0)
OK retriedFrom lineage recorded
OK mission section present after retry (relative path resolved)
OK retry of missing run exits 4 (exit 4)
OK skill install bundled ms-tools (exit 0)
OK installed to skills-available
OK double install refuses (exit 1)
OK activate enables skill (exit 0)
OK enabled dir populated
OK uninstall while enabled refuses (exit 1)
OK deactivate moves back to available (exit 0)
OK uninstall removes from available (exit 0)
OK seat with enabled skill launches
OK skill path delivered to adapter
OK shipped researcher contract resolves (ceiling + network)
OK resolve-role refuses a non-role document (conductor policy) (exit 2)
OK resolve-role refuses name/filename mismatch (exit 2)
OK resolve-role refuses unknown network declaration (exit 2)
OK resolve-role refuses duplicate role tool (exit 2)
OK resolve-role refuses unsupported tool (exit 2)
OK resolve-role refuses missing contract file (exit 4)
OK version 1 role prints no contract line
OK shipped version 2 roles resolve with their contracts (pm, cto, coder, reviewer)
OK resolve-role accepts a minimal version 2 role (exit 0)
OK resolve-role refuses an action outside the vocabulary (exit 2)
OK resolve-role refuses a gated-only action in a role file (exit 2)
OK resolve-role refuses a Vikunja verb no role may hold (exit 2)
OK resolve-role refuses a version 2 role whose contract is missing (exit 2)
OK resolve-role refuses a contract that is a symbolic link (exit 2)
OK seat launches under role ceiling
OK role ceiling narrows seat tools (read,write,bash -> read,bash)
OK narrowing recorded loudly
OK missing role contract refuses launch (exit 2)
OK missing-contract refusal names the role
OK empty ceiling intersection -> tool-free seat
OK tool-free outcome recorded loudly
OK overridden agents dir without seat definition refuses (exit 4)
OK seat-resolution refusal names the seat
OK policy: mission only -> mission tools
OK policy: task only -> task tools
OK policy: both -> intersection (task narrowed)
OK policy: empty intersection -> tool-free
OK invalid mission capabilities rejected (exit 2)
mosaic-task: unsupported mission tool: "sudo" (supported: read, write, edit, bash, grep, find, ls)
OK task run with user layer present (exit 0)
OK user context dispatched into generated prompt
OK workspace+tools task runs via mock (exit 0)
OK workspace path + tools delivered to adapter
OK persistent workspace created on host
OK plain task still runs (no workspace/tools) (exit 0)
OK workspace var present but empty when absent
OK tools empty when absent
OK unknown tool exits 2 (exit 2)
OK workspace traversal exits 2 (exit 2)
OK live hello task succeeds with exact marker
OK result.json written in run dir
OK result.json contents are correct
OK wrong expectExact fails with exit 1 (reason: expect-mismatch)
OK each run gets a distinct run dir (no clobber)
OK list shows both runs
OK fork base: teach succeeds (exit 0)
OK fork child recalls ancestor context (exit 0)
OK forked child recalled ancestor code word
OK ancestor session untouched by fork
OK child session dir has its own branch file
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
OK user recall run succeeds
OK recalled user name (response: Jason)
OK no agent identity on headless run
selftest: 98 passed, 0 failed
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Row 56 mutants: each line applies one perl substitution to one file, runs
# the packages/runs tests, and expects a failure (killed). Restores after.
cd /mnt/storage/scratch/rocko-r56/tree || exit 4
G=/mnt/storage/scratch/rocko-r56/gate
: > "$G/mutants.txt"
mut() { # id file perl-expr
local id="$1" f="$2" e="$3"
cp "$f" "$f.orig"
perl -0pi -e "$e" "$f"
if cmp -s "$f" "$f.orig"; then echo "$id NOT-APPLIED" >> "$G/mutants.txt"; mv -f "$f.orig" "$f"; return; fi
if timeout 300 env -u NODE_TEST_CONTEXT node --test 'packages/runs/tests/*.test.mjs' > "$G/mut-$id.log" 2>&1; then
echo "$id SURVIVED" >> "$G/mutants.txt"
else
echo "$id killed" >> "$G/mutants.txt"
fi
mv -f "$f.orig" "$f"
}
P=packages/runs/src/paths.mjs; R=packages/runs/src/runs.mjs; S=packages/runs/src/state.mjs; T=scripts/mosaic-task.mjs
mut M01 $P 's/return relative === "" \|\|/return true ||/'
mut M02 $P 's/relative !== "\.\." && //'
mut M03 $P 's/startsWith\(`\.\.\$\{path\.sep\}`\)/startsWith("..x")/'
mut M04 $P 's/ && !path\.isAbsolute\(relative\)\)/)/'
mut M05 $P 's/ && !Array\.isArray\(value\) \? value/ ? value/'
mut M06 $P 's/error\?\.code === "ENOENT" \|\| error\?\.code === "ENOTDIR"/error?.code === "ENOENT"/'
mut M07 $P 's/if \(isMissing\(error\)\) return null;\n throw new RunsError\(`cannot resolve/return null;\n throw new RunsError(`cannot resolve/'
mut M08 $P 's/if \(!isInside\(root, real\)\)/if (false)/'
mut M09 $R 's/filter\(\(name\) => name\.startsWith\("r-"\)\)\.sort\(\)/filter((name) => name.startsWith("r-"))/'
mut M10 $R 's/filter\(\(name\) => name\.startsWith\("r-"\)\)/filter(() => true)/'
mut M11 $R 's/if \(isMissing\(error\)\) return \[\];\n throw/return [];\n throw/'
mut M12 $R 's/if \(!RUN_DOCUMENTS\.includes\(name\)\) throw/if (false) throw/'
mut M13 $R 's/export function readRunRecord\(dataRoot, runId\) \{\n requireRunId\(runId\);/export function readRunRecord(dataRoot, runId) {/'
mut M14 $R 's/if \(resolveInside\(dataRoot, RUNS_DIRNAME\) === null\) return null;\n//'
mut M15 $R 's/if \(isMissing\(error\)\) return null;\n throw new RunsError\(`cannot read \$\{dir\}/return null;\n throw new RunsError(`cannot read ${dir}/'
mut M16 $R 's/RUN_ID_PATTERN = \/\^r-/RUN_ID_PATTERN = \/r-/'
mut M17 $S 's/if \(!POINTER_KEYS\.includes\(key\)\) throw/if (false) throw/'
mut M18 $S 's/if \(pointer\.pointerVersion !== 1\)/if (false)/'
mut M19 $S 's/new RunsError\(`release pointer \$\{why\} \(\$\{state\.file\}\)`, 2\)/new RunsError(`release pointer ${why} (${state.file})`)/'
mut M20 $S 's/if \(Object\.keys\(entry\)\.some\(\(key\) => !LOG_KEYS\.includes\(key\)\)\) return null;//'
mut M21 $S 's/if \(entry\.note !== undefined && typeof entry\.note !== "string"\) return null;//'
mut M22 $S 's/entries\.slice\(-last\)/entries.slice(0, last)/'
mut M23 $S 's/if \(line\.trim\(\) === ""\) continue;//'
mut M24 $S 's/if \(last !== undefined && \(!Number\.isInteger\(last\) \|\| last < 1\)\)/if (false)/'
mut M25 $S 's/ throw new RunsError\(`cannot read \$\{file\}/ return null; throw new RunsError(`cannot read ${file}/'
mut M26 $S 's/if \(!isNonEmptyString\(pointer\[key\]\)\)/if (pointer[key] === undefined)/'
mut M27 $S 's/\["at", "event", "release", "imageTag"\]\.every/["event"].every/'
mut M28 $T 's/ if \(result\) \{\n status = result\.status;/ if (true) {\n status = result.status;/'
mut M29 $T 's/ if \(record === null\) \{\n fail\(4, `run not found/ if (false) {\n fail(4, `run not found/'
mut M30 $T 's/if \(error instanceof RunsError\) fail\(error\.exitCode, error\.message\);/if (false) fail(error.exitCode, error.message);/'
mut M31 $T 's/ if \(!isRunId\(runId\)\) \{\n fail\(4, `invalid run id/ if (false) {\n fail(4, `invalid run id/'
mut M32 $T 's/artifacts\.join\(", "\)/artifacts.join(",")/'
echo done >> "$G/mutants.txt"
@@ -0,0 +1,33 @@
M01 killed
M02 killed
M03 killed
M04 SURVIVED
M05 killed
M06 killed
M07 killed
M08 killed
M09 killed
M10 killed
M11 killed
M12 killed
M13 killed
M14 killed
M15 killed
M16 killed
M17 killed
M18 killed
M19 killed
M20 killed
M21 killed
M22 killed
M23 killed
M24 killed
M25 killed
M26 killed
M27 killed
M28 killed
M29 killed
M30 killed
M31 SURVIVED
M32 killed
done
@@ -0,0 +1,46 @@
✔ isRunId accepts the run id shape and nothing else (0.965437ms)
✔ a missing data root or runs directory lists nothing (1.475094ms)
✔ runs as a regular file lists nothing, as before (0.530968ms)
✔ listRunIds keeps r- names only, sorted oldest first (0.699171ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (1.781005ms)
✔ readRunRecord returns documents and artifacts in directory order (0.916419ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (0.587172ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (0.652461ms)
✔ readRunDocument reads only the three run documents (0.499342ms)
✔ a link inside the data root is followed (0.889387ms)
✔ a data root that is itself a link is trusted as configured (0.553199ms)
✔ a run directory linked out of the data root is never read (0.575368ms)
✔ a document linked out of the data root reads as null (0.894385ms)
✔ a runs directory linked out of the data root refuses (0.51067ms)
✔ a relative link that climbs out of the data root refuses (0.396113ms)
✔ a sibling whose name starts with the data root's name is outside it (0.499228ms)
✔ a link loop refuses instead of reading as missing (0.684475ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.383259ms)
✔ an unreadable run directory refuses instead of reading as missing (0.467145ms)
✔ a link to the data root's parent is outside it (0.369657ms)
✔ listRunIds sorts whatever order the directory returns (0.670714ms)
✔ the readers write nothing (1.473542ms)
✔ no pointer is null (1.564788ms)
✔ the pointer release.sh writes reads back (1.031358ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (1.483582ms)
✔ a pointer that is a directory refuses with exit 4 (0.499812ms)
✔ a state file linked out of the data root refuses (1.409385ms)
✔ a state directory linked out of the data root refuses (0.530763ms)
✔ a missing log is empty (0.381369ms)
✔ the log reads oldest first and counts malformed lines (0.826565ms)
✔ last must be a positive integer (0.441409ms)
✔ the state readers write nothing (0.824495ms)
✔ list prints each run in the established format (71.57906ms)
✔ show prints the run in the established format (64.020206ms)
✔ show of a missing run and an invalid id exit 4 as before (101.737552ms)
✔ list and show refuse a runs directory linked out of the data root (131.150958ms)
✔ show refuses a run linked out of the data root; list reports it unknown (134.659276ms)
ℹ tests 37
ℹ suites 0
ℹ pass 37
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 547.924201
rc=0
@@ -0,0 +1,156 @@
diff --git tree/agents/rocko/work/queue-56/delta-check.sh r2/agents/rocko/work/queue-56/delta-check.sh
index 69224ea..a502d1d 100755
--- tree/agents/rocko/work/queue-56/delta-check.sh
+++ r2/agents/rocko/work/queue-56/delta-check.sh
@@ -59,4 +59,7 @@ if [ "$(id -u)" != 0 ]; then
hdr "runs directory unreadable: list"; run_case unreadable list
hdr "runs directory unreadable: show"; run_case unreadable show "$A"
chmod 755 "$D/runs"
+ D="$(root run-unreadable)"; mkdir -p "$D/runs/$A"; printf '%s\n' "$RESULT" > "$D/runs/$A/result.json"; chmod 000 "$D/runs/$A"
+ hdr "run directory unreadable: show"; run_case run-unreadable show "$A"
+ chmod 755 "$D/runs/$A"
fi
diff --git tree/packages/runs/README.md r2/packages/runs/README.md
index 84230a1..8fd7808 100644
--- tree/packages/runs/README.md
+++ r2/packages/runs/README.md
@@ -40,7 +40,11 @@ printed them.
- a run document resolving outside reads as `null`;
- `active.json` or `activation-log.jsonl` resolving outside refuses.
- A link that stays inside the data root is followed.
+ Only a path that exists can resolve. A link with nothing behind it reads
+ as missing wherever it points. A `state/` link out of the data root to a
+ path that doesn't exist reads as no release and an empty log, and a
+ `runs/` link like that lists nothing. A link that stays inside the data
+ root is followed.
- **Run documents are JSON objects or `null`.** A document that is
missing, unreadable, not JSON, or JSON but not an object (`null`, `5`,
`[]`) reads as `null`. Run records are never validated beyond that,
@@ -49,11 +53,14 @@ printed them.
shape `scripts/release.sh` writes: exactly `pointerVersion` 1 and
non-empty strings `release`, `imageTag` and `activatedAt`. Anything else
refuses with exit code 2 rather than being guessed at.
-- **The activation log is lenient per line.** An entry needs string `at`,
- `event`, `release` and `imageTag`, an optional string `note`, and no
- other keys. Other lines are counted in `malformed` and skipped, the way
- `release.sh rollback` skips them. Blank lines aren't counted. `last`
- must be a positive integer and keeps the newest entries.
+- **The activation log is read per line.** One bad line never refuses
+ the log. An entry needs string `at`, `event`, `release` and `imageTag`,
+ an optional string `note`, and no other keys. A line that isn't JSON,
+ or is JSON with another shape, is counted in `malformed` and skipped.
+ That is stricter than `release.sh rollback`, which skips only lines that
+ aren't JSON and would act on an entry with an extra key or a non-string
+ field. Blank lines aren't counted. `last` must be a positive integer and
+ keeps the newest entries.
- **Errors.** Every refusal is a `RunsError` with `exitCode` 2 (invalid
data) or 4 (a file or environment problem), matching
`mosaic-task.mjs`. A missing data root, `runs/` or `state/` file is not
@@ -78,6 +85,7 @@ output changes on purpose:
| `task.json` or `mission.json` is JSON but not an object | `show` printed its snapshot line | snapshot line omitted |
| run is a regular file | `show` crashed | `run not found`, exit 4 |
| `runs/` unreadable | `list` printed nothing | refuses, exit 4 |
+| run directory unreadable (mode 000) | `show` printed two lines, then crashed, exit 1 | refuses with EACCES, exit 4 |
| link loop or a permission error resolving a run | `run not found` | refuses with the error code, exit 4 |
`agents/rocko/work/queue-56/delta-check.sh` prints the before and after
diff --git tree/packages/runs/src/state.mjs r2/packages/runs/src/state.mjs
index a92c871..af5cc92 100644
--- tree/packages/runs/src/state.mjs
+++ r2/packages/runs/src/state.mjs
@@ -60,10 +60,11 @@ function logEntry(line) {
return entry;
}
-// The activation log as { entries, malformed }, oldest first. Lines that
-// aren't a well-formed entry are counted in malformed and skipped, the way
-// release.sh rollback skips them. A missing log is empty. With last, only
-// the newest last well-formed entries are returned.
+// The activation log as { entries, malformed }, oldest first. A line that
+// isn't JSON, or is JSON but not the entry shape release.sh writes, is
+// counted in malformed and skipped. This is stricter than release.sh
+// rollback, which skips only lines that aren't JSON. A missing log is empty.
+// With last, only the newest last well-formed entries are returned.
export function readActivationLog(dataRoot, { last } = {}) {
if (last !== undefined && (!Number.isInteger(last) || last < 1)) {
throw new RunsError(`last must be a positive integer (got ${JSON.stringify(last)})`);
diff --git tree/packages/runs/tests/runs.test.mjs r2/packages/runs/tests/runs.test.mjs
index 85838e5..a652f62 100644
--- tree/packages/runs/tests/runs.test.mjs
+++ r2/packages/runs/tests/runs.test.mjs
@@ -216,6 +216,15 @@ test("listRunIds sorts whatever order the directory returns", (t) => {
assert.deepEqual(listRunIds(dataRoot), [A, B]);
});
+// list has always shown any r- name, including ones show refuses as ids.
+test("listRunRecords lists an r- name that isn't a valid run id, as before", (t) => {
+ const { dataRoot } = scratch(t);
+ write(path.join(dataRoot, "runs", "r-.bad", "result.json"), {});
+ assert.equal(isRunId("r-.bad"), false);
+ assert.deepEqual(listRunIds(dataRoot), ["r-.bad"]);
+ assert.deepEqual(listRunRecords(dataRoot), [{ runId: "r-.bad", result: {} }]);
+});
+
test("the readers write nothing", (t) => {
const { dataRoot } = scratch(t);
const runs = path.join(dataRoot, "runs");
diff --git tree/packages/runs/tests/state.test.mjs r2/packages/runs/tests/state.test.mjs
index 80ab5ea..4b126f3 100644
--- tree/packages/runs/tests/state.test.mjs
+++ r2/packages/runs/tests/state.test.mjs
@@ -72,6 +72,13 @@ test("a state directory linked out of the data root refuses", (t) => {
assert.throws(() => readActivePointer(dataRoot), /state\/active.json resolves outside the data root/);
});
+test("a state directory linked out to nothing reads as no release and an empty log", (t) => {
+ const { dataRoot, outside } = scratch(t);
+ fs.symlinkSync(path.join(outside, "absent"), path.join(dataRoot, "state"));
+ assert.equal(readActivePointer(dataRoot), null);
+ assert.deepEqual(readActivationLog(dataRoot), { entries: [], malformed: 0 });
+});
+
test("a missing log is empty", (t) => {
const { dataRoot } = scratch(t);
assert.deepEqual(readActivationLog(dataRoot), { entries: [], malformed: 0 });
@@ -95,11 +102,13 @@ test("the log reads oldest first and counts malformed lines", (t) => {
{ ...good[1], extra: true },
{ ...good[1], at: 5 },
{ ...good[1], note: null },
+ { at: good[1].at, event: good[1].event, release: good[1].release },
+ { ...good[1], release: 5 },
good[2],
good[3],
]);
- assert.deepEqual(readActivationLog(dataRoot), { entries: good, malformed: 5 });
- assert.deepEqual(readActivationLog(dataRoot, { last: 2 }), { entries: good.slice(2), malformed: 5 });
+ assert.deepEqual(readActivationLog(dataRoot), { entries: good, malformed: 7 });
+ assert.deepEqual(readActivationLog(dataRoot, { last: 2 }), { entries: good.slice(2), malformed: 7 });
assert.deepEqual(readActivationLog(dataRoot, { last: 99 }).entries, good);
});
diff --git tree/packages/runs/tests/task-cli.test.mjs r2/packages/runs/tests/task-cli.test.mjs
index b5fafd4..ed13ecd 100644
--- tree/packages/runs/tests/task-cli.test.mjs
+++ r2/packages/runs/tests/task-cli.test.mjs
@@ -92,3 +92,20 @@ test("show refuses a run linked out of the data root; list reports it unknown",
assert.equal(list.status, 0, list.stderr);
assert.equal(list.stdout, `${A} unknown task=- ws=- session=-\n`);
});
+
+test("list shows an r- name that isn't a valid run id, as before", (t) => {
+ const { dataRoot } = scratch(t);
+ write(path.join(dataRoot, "runs", "r-.bad", "result.json"), RESULT);
+ const out = task(t, dataRoot, "list");
+ assert.equal(out.status, 0, out.stderr);
+ assert.equal(out.stdout, "r-.bad succeeded task=t-one ws=- session=-\n");
+});
+
+test("show refuses an invalid id before reading the config", (t) => {
+ const { dir } = scratch(t);
+ const env = { ...process.env, MOSAIC_CONFIG: path.join(dir, "missing.json") };
+ delete env.NODE_TEST_CONTEXT;
+ const proc = spawnSync(process.execPath, [path.join(ROOT, "scripts", "mosaic-task.mjs"), "show", "../x"], { cwd: ROOT, env, encoding: "utf8" });
+ assert.equal(proc.status, 4, proc.stderr);
+ assert.equal(proc.stderr, 'mosaic-task: invalid run id: "../x" (expected r-<id>)\n');
+});
@@ -0,0 +1,50 @@
✔ isRunId accepts the run id shape and nothing else (1.673322ms)
✔ a missing data root or runs directory lists nothing (2.484645ms)
✔ runs as a regular file lists nothing, as before (0.904005ms)
✔ listRunIds keeps r- names only, sorted oldest first (1.157425ms)
✔ listRunRecords returns each result, or null for an incomplete or unreadable one (2.412977ms)
✔ readRunRecord returns documents and artifacts in directory order (1.628667ms)
✔ readRunRecord is null for a missing run, a dangling link or a file (1.084271ms)
✔ readRunRecord and readRunDocument refuse an invalid run id before touching the disk (1.044359ms)
✔ readRunDocument reads only the three run documents (0.837895ms)
✔ a link inside the data root is followed (1.571328ms)
✔ a data root that is itself a link is trusted as configured (0.9115ms)
✔ a run directory linked out of the data root is never read (1.032763ms)
✔ a document linked out of the data root reads as null (1.651968ms)
✔ a runs directory linked out of the data root refuses (0.9743ms)
✔ a relative link that climbs out of the data root refuses (0.753436ms)
✔ a sibling whose name starts with the data root's name is outside it (0.869964ms)
✔ a link loop refuses instead of reading as missing (0.998801ms)
✔ an unreadable runs directory refuses instead of listing nothing (0.706462ms)
✔ an unreadable run directory refuses instead of reading as missing (0.727334ms)
✔ a link to the data root's parent is outside it (0.619388ms)
✔ listRunIds sorts whatever order the directory returns (1.204458ms)
✔ listRunRecords lists an r- name that isn't a valid run id, as before (0.759264ms)
✔ the readers write nothing (3.124064ms)
✔ no pointer is null (2.06006ms)
✔ the pointer release.sh writes reads back (1.253317ms)
✔ a pointer that isn't the version 1 shape refuses with exit 2 (2.510411ms)
✔ a pointer that is a directory refuses with exit 4 (1.188753ms)
✔ a state file linked out of the data root refuses (1.074534ms)
✔ a state directory linked out of the data root refuses (0.628466ms)
✔ a state directory linked out to nothing reads as no release and an empty log (0.568961ms)
✔ a missing log is empty (0.501754ms)
✔ the log reads oldest first and counts malformed lines (1.269789ms)
✔ last must be a positive integer (0.577624ms)
✔ the state readers write nothing (0.939778ms)
✔ list prints each run in the established format (109.147436ms)
✔ show prints the run in the established format (100.576139ms)
✔ show of a missing run and an invalid id exit 4 as before (145.04362ms)
✔ list and show refuse a runs directory linked out of the data root (172.596296ms)
✔ show refuses a run linked out of the data root; list reports it unknown (169.239482ms)
✔ list shows an r- name that isn't a valid run id, as before (86.181593ms)
✔ show refuses an invalid id before reading the config (58.252566ms)
ℹ tests 41
ℹ suites 0
ℹ pass 41
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 911.848651
rc=0
@@ -0,0 +1,42 @@
6b8358f91b7512a5514d21e32020d7bb51a526c37c6989be93f9e3d608781189 ./base.txt
ad2ee26c16bd13b5d78a5bcedff53b1e5c5e4e11560270cb23d99bc5ca23e289 ./BUILD.md
d2fa5fb7f19ab328b51bf0e8a42beb419914ec6f99cb022cc46e88666a0cdca7 ./build.patch
1afe07062349b097b955f0391bffb839b083492d02644421def6c39cc19ff4a4 ./byte-check.sh
2727198f7e50b935205546a60ea97d8d4f20d87cad2a88e10477148c8d3dc42e ./candidate-manifest.sha256
09788e902a54207ef5f94de1c8b7f2f31c049fb028fa68847115da83060d90f9 ./delta-check.sh
9bbbf163e4625fc966b4f8368b7a578c752f320a0cf9cb82dc63b49c8a055630 ./files.txt
ffeb69dfd69070bc54ac31543737d0f669b5f44f1546919d47370a61f52e2bb9 ./out/apply-75ab1646.txt
0bf93f746d1c2fa0cc9fd38ac63ade144d946f0588e9a74add9dc4ac19a1aae4 ./out/byte-check.txt
9fe4d344ec31b3a0e7f138f7e1cca4a4d01c7c7b7693386fb87b2573acb29a34 ./out/delta-check.txt
422d525e9cdcdfe26156b6c4f9ae8b3bcee0715b2c618b8de459ec634750f87d ./out/gate/test-task.log
e00191a5e3c4998ef63061a02f46b3eea8c1e086f1c309a2aaa95191bd7fae1e ./out/mutants.sh
486c2f735ceb41ab070d778d742662e7a25a6a7b325a57fbc1ce9aedef0f2e5c ./out/mutants.txt
afe2b2f318df2962e739f72ee191e8d214c4f4d5f17d474fce0ae34d0ca306ad ./out/round1/apply-923957e2.txt
10c6593fe07f295f7bbe209e608664d8308fbe663a6160353b130d888d39e000 ./out/round1/apply-dc96f87b.txt
0bf93f746d1c2fa0cc9fd38ac63ade144d946f0588e9a74add9dc4ac19a1aae4 ./out/round1/byte-check.txt
7b1dca80e97d7b0660a38d3daf5f4454380c730a397da652ac9af23dfb3eadb6 ./out/round1/delta-check.txt
d81f2ce71f7c9bc3be43f38bb50d4fcea40bc99d099a629c07e6f449ea0e362f ./out/round1/gate/run.sh
1f6649145018f4f9b298a98c10b3c0b471214d9cbd0c19a41ce5046a47f41800 ./out/round1/gate/runs.log
7278e43ff0a3d60f45c2ed211c25729c105dbf7a9df76eeebd388ea4d73615b6 ./out/round1/gate/summary.txt
f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 ./out/round1/gate/test-auth.log
56c85ba7965e49a10acc80f934874d4be14ae80af81efa72561a293850a2eae2 ./out/round1/gate/test-conductor.log
52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 ./out/round1/gate/test-config.log
c2cdde34b9447351324f2e3e9c160db0c3ff6249057099214218e7737aa7ceb8 ./out/round1/gate/test-discord.log
7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe ./out/round1/gate/test-discord-r2.log
ca044dc45b79206743aba8e22d84d9eecba146c518458dc97a3325cf93eba493 ./out/round1/gate/test-extension-package.log
83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f ./out/round1/gate/test-foundation.log
d86110a3cbaf73c6f8032ae9ff1b737ca8b405281f17673de5b0b8b33a558a14 ./out/round1/gate/test-queue.log
fa832794ee35025fb4559d58d01a270f6bff2eb65e863e9075b797f47fa68545 ./out/round1/gate/test-release.log
ccea37f4ffde1f160dcd1a1864988083a645d974e892065c956b338d0f8c853a ./out/round1/gate/test-task.log
faf3049f543179face1645e81ad64c7172b4d77b40dad0cbc6211bff1630619d ./out/round1/mutants.sh
e4bdafcbe4ef48ae1676c5573d340c46b072e1ae285fc917efd03768bd784192 ./out/round1/mutants.txt
010e2694eb37248355025ee1c6b7295b184a302991862722713a43f45e31b26c ./out/round1/runs-tests-final.log
6462e1c49fa3bde202f453d17ffa9dc32fd2a8d73fb8c83b66e1855d69c4afed ./out/round2-delta.diff
9ac76c6c297b07f2e88d330e948eb9a42a19d3103c53c6fe6e95402a35bcea83 ./out/runs-tests.log
7a4dae1dff789670061c23adc072504a84db9c8d45847cfa54a6c45501354031 ./r1/base.txt
696923cc43617d89c5501ee72961390fed0f8bb6fcef843970b736be3d049758 ./r1/BUILD.md
dd7b469b5aed4ae0e7eed39495e5a1677c36391e9fc0d254a49272db5168d8f0 ./r1/build.patch
ed3c5392ae43e1c3541c6bab6a2f25826514124f5f68536e5cc330a39cc5d7a3 ./r1/candidate-manifest.sha256
9bbbf163e4625fc966b4f8368b7a578c752f320a0cf9cb82dc63b49c8a055630 ./r1/files.txt
e0497ad1bf92c41c8eec719e655f0d19840550ca5f0c9271b9c76f81d68ed1c3 ./r1/packet-manifest.sha256
15d575cc45313906114f7edda9379b66af7624d9e894bb98812deb7acf5d87ba ./seed.sh
+186
View File
@@ -0,0 +1,186 @@
# Row 56 (#1545): runs and releases reader module, candidate packet, round 1
Author: Rocko. Reviewers: Darkwing and Filbert. Brief:
`docs/plans/2026-10-10_design-implementation.md`, section "Runs and releases
reader module". Base: `dc96f87b` (`base.txt`). `build.patch` also applies
cleanly to `923957e2` (origin/refactor at packet time). No file in this
candidate changed between the two (`out/apply-*.txt`). The packet is
uncommitted. I made no commits, pushes or Gitea calls, and read no token or
private binding.
## Files (`files.txt`, 15)
| File | Change |
|---|---|
| `packages/runs/package.json` | new: `@mosaic/runs`, private, no dependencies |
| `packages/runs/src/errors.mjs` | new: `RunsError(message, exitCode = 4)` |
| `packages/runs/src/paths.mjs` | new: `resolveInside` containment, `readJsonObject` |
| `packages/runs/src/runs.mjs` | new: `isRunId`, `listRunIds`, `listRunRecords`, `readRunDocument`, `readRunRecord` |
| `packages/runs/src/state.mjs` | new: `readActivePointer`, `readActivationLog` |
| `packages/runs/src/index.mjs` | new: re-exports |
| `packages/runs/README.md` | new: what it reads, the limits, the deliberate deltas |
| `packages/runs/tests/*.mjs` | new: 37 tests (runs 22, state 10, task-cli 5) plus helpers |
| `scripts/mosaic-task.mjs` | `list` and `show` read through the module, +18/−27 |
| `agents/rocko/work/queue-56/{seed,byte-check,delta-check}.sh` | the byte-identity and delta checks |
`build.patch` is `git diff --cached --binary dc96f87b` over those files:
+1021/−27. `candidate-manifest.sha256` hashes the 15 files.
Q14 freeze: nothing under `packages/bus`, `packages/tasks`, `packages/cli`,
`scripts/bus-service.sh` or `scripts/mosaic` changes. Apart from the import,
`scripts/mosaic-task.mjs` is the only file under `scripts/` that changes. The
module covers the runs view's needs, but the view itself, `release.sh` and
pruning are untouched.
## Design
- **Containment.** `resolveInside` runs `realpathSync` on the data root and
the target, then applies a `path.relative` check:
- `..`, `../…` or an absolute relative path counts as outside;
- the configured data root is trusted even when it is itself a link;
- links that stay inside it are followed.
- **Outside paths.**
- `runs/`, `state/`, a run directory read by `readRunRecord`, `active.json`
and the activation log refuse when they resolve outside.
- A run document that resolves outside reads as `null`, the way an
unreadable one always has.
- **Missing versus broken.** ENOENT and ENOTDIR read as missing. Anything
else (ELOOP, EACCES) refuses with the error code instead of reading as
empty.
- **Documents.** A run document is a JSON object or `null`. Run records are
not validated further, because older records carry older shapes.
- **Release pointer.** `active.json` is strict: exactly the version 1 shape
`release.sh` writes. Bad data exits 2, and a read error exits 4.
- **Activation log.** The log is lenient per line, like `release.sh
rollback`. It returns `{ entries, malformed }`, and `last` keeps the newest
entries.
- **Errors.** Every refusal is a `RunsError` with exit code 2 or 4.
`mosaic-task.mjs` maps it to `fail(exitCode, message)` through
`readRuns`.
- **Read-only.** No function writes. The "readers write nothing" test and
byte-check's before/after snapshot of the data root both check this.
## Gate (`out/gate/`, sequential, scratch worktree of the candidate)
| Suite | Result |
|---|---|
| packages/runs tests | 34/34 at gate time; 37/37 after the mutant round (`out/runs-tests-final.log`) |
| test-auth | 15 passed, 0 failed |
| test-conductor | 17 passed, 0 failed |
| test-config | 24 passed, 0 failed |
| test-discord | first run 57 passed, 1 failed; rerun 66 passed, 0 failed (below) |
| test-extension-package | 18 passed, 0 failed |
| test-foundation | 44 passed, 0 failed |
| test-queue | 27 passed, 0 failed |
| test-release | 14 passed, 0 failed |
| test-task | 98 passed, 0 failed |
**test-discord's first-run failure was environmental.** The failing case was
"pi binary present at node_modules/.bin/pi for the extension checks". The
scratch worktree has no `node_modules`, and the base worktree lacks it too.
I symlinked the checkout's `node_modules` into the scratch tree and reran
the suite: 66 passed, 0 failed. The extra 8 cases are the extension checks
that the missing binary had skipped. Then I removed the symlink
(`out/gate/test-discord-r2.log`). Nothing in this candidate touches
packages/discord. Sage's rerun in a tree that has `node_modules` should see
66.
After the gate, the only changes were tests, the README table row and the
check scripts. No `src/` or `scripts/mosaic-task.mjs` change came after it.
## Byte identity (`out/byte-check.txt`)
`byte-check.sh BASE CAND WORK` seeds a data root with `seed.sh`. The seed
holds:
- runs a–f;
- a run that is a file;
- an internal link and a dangling link;
- `r-zz.weird_name-1`;
- a non-`r-` name, `.pruned.log` and a stray file.
It runs 16 cases from both trees: `list` on seeded, empty and absent roots,
and `show` on each run, a missing run, `../escape`, no argument and an
absent root. It diffs stdout, stderr and exit codes.
```
byte-check: 16 cases, stdout, stderr and exit identical
byte-check: data root unchanged
```
**Correction to my own harness.** My first rerun in this round passed a
relative WORK_DIR. Every redirect inside the `cd` subshell failed, both trees
recorded exit 1, and the diff still reported "identical": a false pass.
Earlier runs used absolute paths and were not affected. Both scripts now
make WORK_DIR absolute before doing anything else. `byte-check.sh` also
exits 4 if any case leaves no stdout or stderr file. The output above
comes from the fixed script.
## Deliberate deltas (`out/delta-check.txt`)
These cases fall outside the byte-identity domain: a link out of the data
root, a document that is JSON but not an object, a run that is a file, a
link loop, and an unreadable directory. Each one changes on purpose. The
README table lists them, and `delta-check.sh` prints base against candidate:
- run directory linked out: `list` shows `unknown`; `show` refuses, exit 4.
- `result.json` linked out: `list` shows `unknown`; `show` prints
`(missing or unreadable)`.
- `runs/` linked out: `list` and `show` refuse, exit 4.
- `result.json` is `5`: base `list` crashed (exit 1); base `show` printed
`undefined` fields. The candidate gives `unknown` and
`(missing or unreadable)`.
- `task.json` is `[]`: base printed the snapshot line; the candidate
omits it.
- run is a regular file: base `show` crashed in `readdirSync`; the
candidate gives `run not found`, exit 4.
- link loop: base said `run not found`; the candidate refuses with ELOOP,
exit 4.
- `runs/` unreadable: base `list` printed nothing, exit 0; the candidate
refuses with EACCES, exit 4.
## Mutants (`out/mutants.sh`, `out/mutants.txt`)
I ran 32 single-line mutants of `src/` and `scripts/mosaic-task.mjs`
against the packages/runs tests.
First round: 26 killed, 4 survived and 2 didn't apply. Three of the
survivors were real gaps, and I added a test for each:
- **M02** dropped the exact `..` check. A link to the data root's parent got
through. New test: "a link to the data root's parent is outside it".
- **M09** dropped the sort. Node returned this directory already sorted,
so no fixture could catch it. New test: it mocks `fs.readdirSync` to
return the names reversed.
- **M15** read every readdir error on a run directory as missing. New
test: "an unreadable run directory refuses instead of reading as
missing".
Final round: 30 killed, 2 survived. Both survivors are equivalent:
- **M04** dropped `!path.isAbsolute(relative)`. On POSIX, `path.relative`
between two absolute paths is never absolute, so the branch is
unreachable on Linux. I kept it as a guard for other platforms.
- **M31** dropped the CLI's `isRunId` pre-check in `show`. `readRunRecord`
throws the same message with exit 4, and `readRuns` maps it to the same
`fail`. The pre-check stays so that an invalid id refuses before the
config loads, as it did before.
## Follow-up (not fixed, out of scope)
This defect predates the candidate and is unchanged: `list` crashes with
a `padEnd` TypeError when `result.json` is an object without a string
`status` or `taskId`. Base and candidate behave the same here, so
byte-identity holds. A fix would change `list` output and belongs in its
own row.
## Reproduce
```sh
git worktree add --detach /tmp/r56-base dc96f87b
git worktree add --detach /tmp/r56-cand dc96f87b
(cd /tmp/r56-cand && git apply --index <packet>/build.patch && sha256sum -c <packet>/candidate-manifest.sha256)
env -u NODE_TEST_CONTEXT node --test '/tmp/r56-cand/packages/runs/tests/*.test.mjs'
bash /tmp/r56-cand/agents/rocko/work/queue-56/byte-check.sh /tmp/r56-base /tmp/r56-cand /tmp/r56-bc
bash /tmp/r56-cand/agents/rocko/work/queue-56/delta-check.sh /tmp/r56-base /tmp/r56-cand /tmp/r56-dc
```
+1
View File
@@ -0,0 +1 @@
dc96f87b1776cc74d0ea5ba7701db418cc21e1b8
File diff suppressed because it is too large Load Diff
@@ -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
+15
View File
@@ -0,0 +1,15 @@
agents/rocko/work/queue-56/byte-check.sh
agents/rocko/work/queue-56/delta-check.sh
agents/rocko/work/queue-56/seed.sh
packages/runs/package.json
packages/runs/README.md
packages/runs/src/errors.mjs
packages/runs/src/index.mjs
packages/runs/src/paths.mjs
packages/runs/src/runs.mjs
packages/runs/src/state.mjs
packages/runs/tests/helpers.mjs
packages/runs/tests/runs.test.mjs
packages/runs/tests/state.test.mjs
packages/runs/tests/task-cli.test.mjs
scripts/mosaic-task.mjs
@@ -0,0 +1,28 @@
7a4dae1dff789670061c23adc072504a84db9c8d45847cfa54a6c45501354031 ./base.txt
696923cc43617d89c5501ee72961390fed0f8bb6fcef843970b736be3d049758 ./BUILD.md
dd7b469b5aed4ae0e7eed39495e5a1677c36391e9fc0d254a49272db5168d8f0 ./build.patch
1afe07062349b097b955f0391bffb839b083492d02644421def6c39cc19ff4a4 ./byte-check.sh
ed3c5392ae43e1c3541c6bab6a2f25826514124f5f68536e5cc330a39cc5d7a3 ./candidate-manifest.sha256
96416ea2eb84490b79e98b9aa0f95673e76070f15ba2876f83dbf1bdaa919f97 ./delta-check.sh
9bbbf163e4625fc966b4f8368b7a578c752f320a0cf9cb82dc63b49c8a055630 ./files.txt
afe2b2f318df2962e739f72ee191e8d214c4f4d5f17d474fce0ae34d0ca306ad ./out/apply-923957e2.txt
10c6593fe07f295f7bbe209e608664d8308fbe663a6160353b130d888d39e000 ./out/apply-dc96f87b.txt
0bf93f746d1c2fa0cc9fd38ac63ade144d946f0588e9a74add9dc4ac19a1aae4 ./out/byte-check.txt
7b1dca80e97d7b0660a38d3daf5f4454380c730a397da652ac9af23dfb3eadb6 ./out/delta-check.txt
d81f2ce71f7c9bc3be43f38bb50d4fcea40bc99d099a629c07e6f449ea0e362f ./out/gate/run.sh
1f6649145018f4f9b298a98c10b3c0b471214d9cbd0c19a41ce5046a47f41800 ./out/gate/runs.log
7278e43ff0a3d60f45c2ed211c25729c105dbf7a9df76eeebd388ea4d73615b6 ./out/gate/summary.txt
f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 ./out/gate/test-auth.log
56c85ba7965e49a10acc80f934874d4be14ae80af81efa72561a293850a2eae2 ./out/gate/test-conductor.log
52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 ./out/gate/test-config.log
c2cdde34b9447351324f2e3e9c160db0c3ff6249057099214218e7737aa7ceb8 ./out/gate/test-discord.log
7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe ./out/gate/test-discord-r2.log
ca044dc45b79206743aba8e22d84d9eecba146c518458dc97a3325cf93eba493 ./out/gate/test-extension-package.log
83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f ./out/gate/test-foundation.log
d86110a3cbaf73c6f8032ae9ff1b737ca8b405281f17673de5b0b8b33a558a14 ./out/gate/test-queue.log
fa832794ee35025fb4559d58d01a270f6bff2eb65e863e9075b797f47fa68545 ./out/gate/test-release.log
ccea37f4ffde1f160dcd1a1864988083a645d974e892065c956b338d0f8c853a ./out/gate/test-task.log
faf3049f543179face1645e81ad64c7172b4d77b40dad0cbc6211bff1630619d ./out/mutants.sh
e4bdafcbe4ef48ae1676c5573d340c46b072e1ae285fc917efd03768bd784192 ./out/mutants.txt
010e2694eb37248355025ee1c6b7295b184a302991862722713a43f45e31b26c ./out/runs-tests-final.log
15d575cc45313906114f7edda9379b66af7624d9e894bb98812deb7acf5d87ba ./seed.sh