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]>
This commit is contained in:
+18
-27
@@ -35,6 +35,7 @@ import { randomBytes } from "node:crypto";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { BusinessError, validateRoleDocument } from "../packages/business/src/index.mjs";
|
||||
import { RunsError, isRunId, listRunRecords, readRunRecord } from "../packages/runs/src/index.mjs";
|
||||
|
||||
const PROJECT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const RUNS_DIRNAME = "runs";
|
||||
@@ -457,53 +458,43 @@ function runTask(taskFile, options = {}) {
|
||||
process.exit(status === "succeeded" ? 0 : 1);
|
||||
}
|
||||
|
||||
function readRuns(read) {
|
||||
try {
|
||||
return read();
|
||||
} catch (error) {
|
||||
if (error instanceof RunsError) fail(error.exitCode, error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function listRuns() {
|
||||
const resolved = loadConfig();
|
||||
const root = runsRoot(resolved);
|
||||
let entries = [];
|
||||
try {
|
||||
entries = fs.readdirSync(root).filter((name) => name.startsWith("r-")).sort();
|
||||
} catch {
|
||||
// No runs yet.
|
||||
}
|
||||
for (const runId of entries) {
|
||||
for (const { runId, result } of readRuns(() => listRunRecords(resolved.dataRoot))) {
|
||||
// An incomplete or unreadable run record (result null) reports as unknown.
|
||||
let status = "unknown";
|
||||
let taskId = "-";
|
||||
let workspace = "-";
|
||||
let session = "-";
|
||||
try {
|
||||
const result = JSON.parse(fs.readFileSync(path.join(root, runId, "result.json"), "utf8"));
|
||||
if (result) {
|
||||
status = result.status;
|
||||
taskId = result.taskId;
|
||||
workspace = result.workspace ?? "-";
|
||||
session = result.session ?? "-";
|
||||
} catch {
|
||||
// Incomplete run record; report as unknown.
|
||||
}
|
||||
process.stdout.write(`${runId} ${status.padEnd(9)} task=${taskId.padEnd(18)} ws=${String(workspace).padEnd(10)} session=${session}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
function showRun(runId) {
|
||||
if (!/^r-[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(runId)) {
|
||||
if (!isRunId(runId)) {
|
||||
fail(4, `invalid run id: ${JSON.stringify(runId)} (expected r-<id>)`);
|
||||
}
|
||||
const resolved = loadConfig();
|
||||
const dir = path.join(runsRoot(resolved), runId);
|
||||
if (!fs.existsSync(dir)) {
|
||||
const record = readRuns(() => readRunRecord(resolved.dataRoot, runId));
|
||||
if (record === null) {
|
||||
fail(4, `run not found: ${runId} (under ${runsRoot(resolved)})`);
|
||||
}
|
||||
|
||||
const read = (name) => {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(path.join(dir, name), "utf8"));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const result = read("result.json");
|
||||
const task = read("task.json");
|
||||
const mission = read("mission.json");
|
||||
const { result, task, mission } = record;
|
||||
|
||||
process.stdout.write(`run: ${runId}\n`);
|
||||
if (result) {
|
||||
@@ -529,7 +520,7 @@ function showRun(runId) {
|
||||
}
|
||||
if (task) process.stdout.write(`task snapshot: ${"task.json"} present\n`);
|
||||
if (mission) process.stdout.write(`mission snapshot: ${mission.id} - ${mission.objective}\n`);
|
||||
process.stdout.write(`artifacts: ${fs.readdirSync(dir).map((f) => `${f}`).join(", ")}\n`);
|
||||
process.stdout.write(`artifacts: ${record.artifacts.join(", ")}\n`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user