Files
stack/packages/runs/tests/task-cli.test.mjs
T
jason.woltjeandClaude Opus 5.5 71d874764a feat(runs): read-only runs and releases reader module (#1545, row 56)
New packages/runs reads run records, result.json, the release pointer
and the activation log without writing, pruning or following a link out
of the data root. mosaic-task.mjs list and show use it, so a malformed
result.json or a run that is a regular file no longer crashes them.
Deliberate deltas are listed in the README.

Rocko built it. Round 1 (ed3c5392) was approved with notes by Darkwing
(27115) and Filbert (27116); round 2 (2727198f, tests and wording only)
was approved by Darkwing (27123) and Filbert (27124). Sage's gate on
c9a25a47 plus the candidate: runs 41/0, queue 148/0, webui 22/0,
conversation 182/0 (181/1 in the full run on the K12 cgroup timing
test under load, 182/0 alone), control-board 124/0, every
scripts/test-*.sh 0 failed (task 98/0 with Docker, 26/0 without).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-10 12:22:37 -05:00

112 lines
5.0 KiB
JavaScript

// scripts/mosaic-task.mjs list and show read through this package. These
// spawn the real script against a seeded data root and a scratch config.
import { test } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { RESULT, scratch, write } from "./helpers.mjs";
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
const A = "r-20260101T000000Z-aaaaaa";
const B = "r-20260101T000100Z-bbbbbb";
function task(t, dataRoot, ...args) {
const config = path.join(path.dirname(dataRoot), "config.json");
write(config, { configVersion: 1, environment: "development", dataRoot, execution: { backend: "docker", provider: "zai", model: "m" } });
const env = { ...process.env, MOSAIC_CONFIG: config };
delete env.NODE_TEST_CONTEXT;
const proc = spawnSync(process.execPath, [path.join(ROOT, "scripts", "mosaic-task.mjs"), ...args], { cwd: ROOT, env, encoding: "utf8" });
return { status: proc.status, stdout: proc.stdout, stderr: proc.stderr };
}
test("list prints each run in the established format", (t) => {
const { dataRoot } = scratch(t);
write(path.join(dataRoot, "runs", A, "result.json"), { ...RESULT, workspace: "ws1", session: "s1" });
write(path.join(dataRoot, "runs", B, "task.json"), { id: "t" });
const out = task(t, dataRoot, "list");
assert.equal(out.status, 0, out.stderr);
assert.equal(out.stdout,
`${A} succeeded task=t-one ws=ws1 session=s1\n` +
`${B} unknown task=- ws=- session=-\n`);
});
test("show prints the run in the established format", (t) => {
const { dataRoot } = scratch(t);
const dir = path.join(dataRoot, "runs", A);
write(path.join(dir, "result.json"), { ...RESULT, missionId: "m", tools: ["read"], expectedExact: "hi" });
write(path.join(dir, "mission.json"), { id: "m", objective: "obj" });
const out = task(t, dataRoot, "show", A);
assert.equal(out.status, 0, out.stderr);
assert.equal(out.stdout, [
`run: ${A}`,
"status: succeeded",
"task: t-one",
"mission: m",
"tools: read",
"adapter: (see config) provider=zai model=m",
'request: "hi"',
'response: "hi"',
'expected: "hi"',
"timing: 2026-01-01T00:00:00.000Z -> 2026-01-01T00:00:01.000Z (1000 ms)",
"exit: 0",
"mission snapshot: m - obj",
`artifacts: ${fs.readdirSync(dir).join(", ")}`,
"",
].join("\n"));
});
test("show of a missing run and an invalid id exit 4 as before", (t) => {
const { dataRoot } = scratch(t);
const missing = task(t, dataRoot, "show", A);
assert.equal(missing.status, 4);
assert.equal(missing.stderr, `mosaic-task: run not found: ${A} (under ${path.join(dataRoot, "runs")})\n`);
const invalid = task(t, dataRoot, "show", "../x");
assert.equal(invalid.status, 4);
assert.equal(invalid.stderr, 'mosaic-task: invalid run id: "../x" (expected r-<id>)\n');
});
test("list and show refuse a runs directory linked out of the data root", (t) => {
const { dataRoot, outside } = scratch(t);
write(path.join(outside, A, "result.json"), RESULT);
fs.symlinkSync(outside, path.join(dataRoot, "runs"));
for (const args of [["list"], ["show", A]]) {
const out = task(t, dataRoot, ...args);
assert.equal(out.status, 4, args.join(" "));
assert.equal(out.stdout, "");
assert.match(out.stderr, /^mosaic-task: .*runs resolves outside the data root/);
}
});
test("show refuses a run linked out of the data root; list reports it unknown", (t) => {
const { dataRoot, outside } = scratch(t);
write(path.join(outside, "result.json"), RESULT);
fs.mkdirSync(path.join(dataRoot, "runs"));
fs.symlinkSync(outside, path.join(dataRoot, "runs", A));
const show = task(t, dataRoot, "show", A);
assert.equal(show.status, 4);
assert.equal(show.stdout, "");
assert.match(show.stderr, /resolves outside the data root/);
const list = task(t, dataRoot, "list");
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');
});