102 files under agents/sage/work/vikunja-setup/: the comment, close and file scripts Sage ran on P19 and their output records, including P19 386 comment 350 for the mos-0928 Part A window (rows 177 and 180-185). Each script refuses to write a record that holds a key; a scan for both key values found none.
67 lines
5.1 KiB
JavaScript
67 lines
5.1 KiB
JavaScript
// Sage, 2026-10-13: decision 110 and Mos sage-0147. Files the unreproduced test-release activation
|
|
// failure from the row 104 gate as one task in project 19 with Sage's own key (bot user 13). Refuses if
|
|
// the title already exists. The sync key reads it back. No labels, no assignees, no bucket moves.
|
|
// Rollback is marking the task done; deletion is Jason's or ops-01's. Prints no secret.
|
|
// Usage: node file-release-flake.mjs https://tasks.woltje.com SAGE_TOKEN_FILE SYNC_TOKEN_FILE OUT_JSON
|
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
const [ORIGIN, KEY, SYNC, OUT] = process.argv.slice(2);
|
|
if (!/^https:\/\/tasks\.woltje\.com$/.test(ORIGIN)) throw new Error("tasks.woltje.com only");
|
|
if (!OUT || existsSync(OUT)) throw new Error("OUT_JSON missing or exists; refusing");
|
|
const PROJECT = 19;
|
|
const SIG = "Created by Sage (T3 1ef1e4f8) with Sage's own key (bot user 13).";
|
|
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
const p = (...xs) => xs.map((x) => `<p>${esc(x)}</p>`).join("") + `<p>${esc(SIG)}</p>`;
|
|
const TASKS = [
|
|
{ title: "test-release: healthy activation failed once in the row 104 gate, not reproduced",
|
|
description: p("Sage's row 104 gate (#1598), run 2026-10-13 22:25-22:32Z in a detached worktree of f00613106 plus Darkwing's build.patch, DOCKER_HOST unset, Docker 29.7.2. Every package suite and the scripts before it passed. scripts/test-release.sh then went 11 passed, 3 failed and the gate stopped.",
|
|
"Failed checks: (1) \"healthy activation succeeds\" (exit 1, expected 0); (2) \"pointer written with valid fields\" (ENOENT on the sandbox data/state/active.json, because activation wrote no pointer); (3) \"repeat activation succeeds (log grows)\" (exit 1, expected 0). The fault-injection and refusal checks before them passed.",
|
|
"Not reproduced: scripts/release.sh activate alone, with the same sandbox config shape in the same tree, exited 0 (\"activate OK -> 0.0.12\"); a full rerun of test-release.sh in the same tree went 14 passed, 0 failed (22:39Z); test-task.sh then passed 98/0. Row 104 doesn't touch scripts/release.sh or the image. Darkwing's gate of the same candidate on 817ddfea6 passed test-release.",
|
|
"The cause is unknown because expect_exit sends activation's stdout and stderr to /dev/null. First step: keep that output in a file under the sandbox and print it on a failure, so the next occurrence names its reason. Logs: /mnt/storage/scratch/sage-gate-104-out/script-release.log and script-release-rerun.log.",
|
|
"Mos (sage-0147): if it shows up again during the row 104 re-pin gate, stop and tell Mos.") },
|
|
];
|
|
const key = readFileSync(KEY, "utf8").trim(), sync = readFileSync(SYNC, "utf8").trim();
|
|
async function api(method, path, body, auth) {
|
|
const r = await fetch(ORIGIN + path, { method, headers: { Authorization: `Bearer ${auth}`, "Content-Type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body), redirect: "error" });
|
|
const t = await r.text(); let json = null; try { json = JSON.parse(t); } catch {}
|
|
return { status: r.status, json };
|
|
}
|
|
const list = (j) => j?.items ?? (Array.isArray(j) ? j : []);
|
|
async function existing() {
|
|
const out = [];
|
|
for (let page = 1; page < 20; page++) {
|
|
const r = await api("GET", `/api/v2/projects/${PROJECT}/tasks?per_page=50&page=${page}`, undefined, sync);
|
|
if (r.status !== 200) throw new Error(`list tasks: ${r.status}`);
|
|
out.push(...list(r.json));
|
|
if (list(r.json).length < 50) return out;
|
|
}
|
|
throw new Error("more than 19 pages");
|
|
}
|
|
const rec = { at: new Date().toISOString(), project: PROJECT, created: [], problems: [] };
|
|
const save = () => {
|
|
const out = JSON.stringify(rec, null, 1) + "\n";
|
|
if (out.includes(key) || out.includes(sync)) throw new Error("secret in the record; not written");
|
|
writeFileSync(OUT, out);
|
|
};
|
|
const before = await existing();
|
|
const clash = before.filter((t) => TASKS.some((x) => x.title === t.title));
|
|
if (clash.length) throw new Error(`exists: ${clash.map((t) => `#${t.id} ${t.title}`).join("; ")}; refusing`);
|
|
writeFileSync(OUT, "", { flag: "wx" });
|
|
|
|
for (const t of TASKS) {
|
|
rec.created.push({ title: t.title });
|
|
save();
|
|
const body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
|
|
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, key);
|
|
if (r.status >= 300 || !Number.isSafeInteger(r.json?.id)) {
|
|
rec.problems.push(`create ${t.title}: ${r.status} ${r.json?.code ?? ""} ${r.json?.message ?? ""}`);
|
|
break;
|
|
}
|
|
Object.assign(rec.created.at(-1), { id: r.json.id, index: r.json.index, inverse: `mark done: POST /api/v1/tasks/${r.json.id} done true (pm); delete is Jason's or ops-01's` });
|
|
const back = await api("GET", `/api/v2/tasks/${r.json.id}`, undefined, sync);
|
|
Object.assign(rec.created.at(-1), { readBack: back.status, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
|
|
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
|
|
}
|
|
save();
|
|
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
|
|
if (rec.problems.length) process.exit(2);
|