Files
stack/agents/sage/work/vikunja-setup/file-webui-flake.mjs
jason.woltje e24a13ad87 sage: Vikunja scripts and read-back records 2026-10-13 to 10-20 (decision 110, mos-0882)
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.
2026-10-20 03:32:46 -05:00

73 lines
6.5 KiB
JavaScript

// Sage, 2026-10-14: decision 110. Files the two unreproduced one-off failures from the row 110 round 2 gate (a webui
// browser test and a test-task model recall check) as two tasks 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-webui-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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
const p = (...xs) => xs.map((x) => `<p>${esc(x)}</p>`).join("") + `<p>${esc(SIG)}</p>`;
const TASKS = [
{ title: "webui: redirect sign-out browser test timed out once in the row 110 gate, not reproduced",
description: p("Sage's row 110 round 2 gate (#1604), run 2026-10-14 01:29:38-01:32:15Z in a detached worktree of badfa52b9 plus Darkwing's round 2 build.patch (manifest bdaf7f47). Every package suite before webui passed. packages/webui then went 107 passed, 1 failed and the gate stopped.",
"Failed test: \"a redirect answer on a Board read, a bus read, a conversation read, a board POST, a module read and a send signs the page out: one line, nothing stale, no further reads\" (board-held.test.mjs:220, via out() at :168). Error: Condition timed out: !document.querySelector(\"#signed-out\").hidden, after 9313 ms. Load average was about 4.8 at the time.",
"Not reproduced: two webui suite reruns in the same worktree went 108 passed, 0 failed each (ending 01:34:47Z). A second whole-gate run in the same tree passed webui 108/0 and every other package suite. Row 110 doesn't touch packages/webui. Darkwing's round 1 and round 2 gates of the row passed it.",
"The cause is unknown. A first step is to log which of the six redirect cases was waiting when the condition timed out, so the next occurrence names it. Logs: /mnt/storage/scratch/sage-gate-110r2-out/try1/pkg-webui.log and webui-rerun1.log, webui-rerun2.log.",
"Same rule as #258 and #261 (Mos sage-0150): one recurrence in a gate is a note; two in the same gate mean stop and tell Mos.") },
{ title: "test-task: forked child did not recall the ancestor code word once in the row 110 gate, not reproduced",
description: p("Sage's row 110 round 2 gate (#1604), second run, 2026-10-14, in a detached worktree of badfa52b9 plus Darkwing's round 2 build.patch (manifest bdaf7f47). Every package suite and every script before it passed. scripts/test-task.sh then went 97 passed, 1 failed.",
"Failed check: \"forked child recalled ancestor code word\" (scripts/test-task.sh:486). The checks either side passed: \"fork base: teach succeeds\" and \"fork child recalls ancestor context\" (exit 0), then \"ancestor session untouched by fork\". So the child run succeeded, but its result.json response did not contain mosaico. The check calls a real model, so the reply can vary.",
"Not reproduced: test-task.sh alone in the same tree then went 98 passed, 0 failed. Row 110 doesn't touch run-task.sh, the pi adapter or sessions. Darkwing's round 1 and round 2 gates passed it.",
"The cause is unknown because the suite's sandbox (mktemp, removed by an EXIT trap) took the run record with it. First step: print the child's response on a failure, so the next occurrence shows what the model said. Logs: /mnt/storage/scratch/sage-gate-110r2-out/try2/script-task.log and script-task-rerun.log.",
"Same rule as #258 and #261 (Mos sage-0150): one recurrence in a gate is a note; two in the same gate mean 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);