diff --git a/packages/runs/README.md b/packages/runs/README.md index 8fd78082c..9718bed4d 100644 --- a/packages/runs/README.md +++ b/packages/runs/README.md @@ -72,7 +72,10 @@ printed them. `list` and `show` print exactly what they printed before this package existed, for any data root with no link out of it, no run document that -is JSON but not an object, and no unreadable directory. +is JSON but not an object, no `result.json` with a field of the wrong +type (a `status` or `taskId` that isn't a string, or a `workspace` or +`session` that is neither a string nor `null`), and no unreadable +directory. `agents/rocko/work/queue-56/byte-check.sh` checks that byte for byte against a seeded data root. Where those conditions don't hold, the output changes on purpose: @@ -82,6 +85,7 @@ output changes on purpose: | run directory or `result.json` linked out | read through the link | `list`: `unknown`; `show`: refuses (run directory) or `result.json: (missing or unreadable)` | | `runs/` linked out | read through the link | `list` and `show` refuse, exit 4 | | `result.json` is `5` or `[]` | `list` crashed; `show` printed `undefined` fields | `unknown`; `(missing or unreadable)` | +| `result.json` is an object whose `status` or `taskId` isn't a string, or whose `workspace` or `session` is neither a string nor `null` (`{}`, a numeric `status`, a `null` `taskId`) | `list` crashed on a `status` or `taskId` that isn't a string, hiding every run after it, and printed a `workspace` or `session` that is neither a string nor `null` through `String` | `list`: `unknown` for the status, `-` for each other field, and every later run still lists; `show` as before | | `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 | diff --git a/packages/runs/tests/task-cli.test.mjs b/packages/runs/tests/task-cli.test.mjs index ed13ecde6..a7c1caed3 100644 --- a/packages/runs/tests/task-cli.test.mjs +++ b/packages/runs/tests/task-cli.test.mjs @@ -32,6 +32,34 @@ test("list prints each run in the established format", (t) => { `${B} unknown task=- ws=- session=-\n`); }); +test("list prints a damaged result.json as unknown and keeps listing the runs after it", (t) => { + const { dataRoot } = scratch(t); + const M = "r-20260101T000050Z-mmmmmm"; + write(path.join(dataRoot, "runs", A, "result.json"), { ...RESULT, workspace: "ws1", session: "s1" }); + write(path.join(dataRoot, "runs", M, "result.json"), {}); + write(path.join(dataRoot, "runs", B, "result.json"), { ...RESULT, taskId: "t-two", workspace: ":run" }); + 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` + + `${M} unknown task=- ws=- session=-\n` + + `${B} succeeded task=t-two ws=:run session=-\n`); +}); + +test("list prints each result field that isn't a string as unknown or -", (t) => { + const { dataRoot } = scratch(t); + const runs = [ + ["r-20260101T000000Z-status", { status: 5 }, "unknown task=t-one ws=- session=-"], + ["r-20260101T000001Z-taskid", { taskId: null }, "succeeded task=- ws=- session=-"], + ["r-20260101T000002Z-worksp", { workspace: 7 }, "succeeded task=t-one ws=- session=-"], + ["r-20260101T000003Z-sessin", { session: { name: "s1" } }, "succeeded task=t-one ws=- session=-"], + ]; + for (const [runId, damage] of runs) write(path.join(dataRoot, "runs", runId, "result.json"), { ...RESULT, ...damage }); + const out = task(t, dataRoot, "list"); + assert.equal(out.status, 0, out.stderr); + assert.equal(out.stdout, runs.map(([runId, , line]) => `${runId} ${line}\n`).join("")); +}); + test("show prints the run in the established format", (t) => { const { dataRoot } = scratch(t); const dir = path.join(dataRoot, "runs", A); diff --git a/scripts/mosaic-task.mjs b/scripts/mosaic-task.mjs index cf8399a0a..fc4e38450 100755 --- a/scripts/mosaic-task.mjs +++ b/scripts/mosaic-task.mjs @@ -470,18 +470,19 @@ function readRuns(read) { function listRuns() { const resolved = loadConfig(); for (const { runId, result } of readRuns(() => listRunRecords(resolved.dataRoot))) { - // An incomplete or unreadable run record (result null) reports as unknown. + // An incomplete or unreadable run record (result null) reports as unknown, + // and so does each field of a damaged one that isn't a string. let status = "unknown"; let taskId = "-"; let workspace = "-"; let session = "-"; if (result) { - status = result.status; - taskId = result.taskId; - workspace = result.workspace ?? "-"; - session = result.session ?? "-"; + if (typeof result.status === "string") status = result.status; + if (typeof result.taskId === "string") taskId = result.taskId; + if (typeof result.workspace === "string") workspace = result.workspace; + if (typeof result.session === "string") session = result.session; } - process.stdout.write(`${runId} ${status.padEnd(9)} task=${taskId.padEnd(18)} ws=${String(workspace).padEnd(10)} session=${session}\n`); + process.stdout.write(`${runId} ${status.padEnd(9)} task=${taskId.padEnd(18)} ws=${workspace.padEnd(10)} session=${session}\n`); } }