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]>
112 lines
5.0 KiB
JavaScript
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');
|
|
});
|