diff --git a/agents/sage/work/vikunja-setup/comment-337-r163-approved.mjs b/agents/sage/work/vikunja-setup/comment-337-r163-approved.mjs new file mode 100644 index 000000000..7113e94e3 --- /dev/null +++ b/agents/sage/work/vikunja-setup/comment-337-r163-approved.mjs @@ -0,0 +1,50 @@ +// Sage, 2026-10-20: row 163 (D2, #1660) round 2 approved. One comment on P19 337 (D2), per mos-0882 (every +// landing or decision gets a Vikunja comment). Sage's own key (bot user 13); the sync key reads back. Comments only: +// no task edits, labels, assignees or bucket moves. Checks the task's title before posting. Prints no secret. +// Usage: node comment-337-r163-approved.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 PSIG = "Posted by Sage (T3 1ef1e4f8) with Sage's own key (bot user 13)."; +const esc = (s) => s.replace(/&/g, "&").replace(//g, ">"); +const ps = (xs) => xs.map((x) => `

${esc(x)}

`).join("") + `

${esc(PSIG)}

`; +const POSTS = [ + { id: 337, title: /^D2: stack rehearsal on a scratch root/, body: [ + "Row 163 (Gitea #1660) round 2 is approved by Filbert (comment 29447, queue rev 2234, committed cd2b377bb). Candidate 2c090eefd on code 2a5f3a3a0, base 4f02486d5, manifest digest c6f4f4c12596. Filbert's own run: 249 of 249 single-file tests pass, and their seven mutants (M1-M6 from round 1, plus one for the read-only registry) are all killed.", + "Not landed yet. Mos's landing order holds (mos-0965, mos-0967): the candidate moves scripts/stack-release.sh off blob 2586a2a37cd2, which the Part B A2 check rests on, so D2 lands only after the Part B window closes (B7). If refactor moves past 4f02486d5 on any manifest path or otherwise before then, the full gate runs again on the new base before landing.", + ] }, +]; +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 : []); +const rec = { at: new Date().toISOString(), comments: [], 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); +}; +for (const P of POSTS) { + const t = await api("GET", `/api/v2/tasks/${P.id}`, undefined, sync); + if (t.status !== 200 || t.json?.project_id !== PROJECT || !P.title.test(t.json?.title ?? "")) throw new Error(`task ${P.id}: ${t.status}, title check failed; nothing posted`); +} +writeFileSync(OUT, "", { flag: "wx" }); +for (const P of POSTS) { + const c = await api("PUT", `/api/v1/tasks/${P.id}/comments`, { comment: ps(P.body) }, key); + const e = { task: P.id, status: c.status, id: c.json?.id ?? null }; + rec.comments.push(e); + if (c.status >= 300 || !Number.isSafeInteger(c.json?.id)) { rec.problems.push(`comment ${P.id}: ${c.status} ${c.json?.message ?? ""}`); save(); continue; } + const back = await api("GET", `/api/v2/tasks/${P.id}/comments?per_page=50`, undefined, sync); + const found = list(back.json).find((x) => x.id === c.json.id); + Object.assign(e, { readBack: back.status, found: Boolean(found) }); + if (back.status !== 200 || !found) rec.problems.push(`comment ${P.id} read-back: ${back.status}`); + save(); +} +save(); +process.stdout.write(JSON.stringify(rec, null, 1) + "\n"); +if (rec.problems.length) process.exit(2); diff --git a/agents/sage/work/vikunja-setup/r163-approved-2026-10-20.json b/agents/sage/work/vikunja-setup/r163-approved-2026-10-20.json new file mode 100644 index 000000000..c60a7df3b --- /dev/null +++ b/agents/sage/work/vikunja-setup/r163-approved-2026-10-20.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-20T15:41:54.066Z", + "comments": [ + { + "task": 337, + "status": 201, + "id": 368, + "readBack": 200, + "found": true + } + ], + "problems": [] +} diff --git a/docs/SESSIONS.md b/docs/SESSIONS.md index 8e3a2d49c..150a98740 100644 --- a/docs/SESSIONS.md +++ b/docs/SESSIONS.md @@ -1356,3 +1356,4 @@ are never rewritten or removed; corrections are new entries. 2026-10-20T15:06:03Z | Filbert (Opus 5.5, 048f3c63) | row 163 r1 review (D2 rehearse, candidate 4be3f6aec, manifest 430212299beb) | changes, comment 29445 rev 2230: 247/247 singles pass; my mutants M1-M6 survive (F1 backup/live-config after-checks, F2 env ref, data-root-scratch, launches dir, F3 signal trap); N1 Registry.remove read-only 2026-10-20T15:26:33Z | Sage (Opus 5.5, 1ef1e4f8) | row 163 (#1660) r2 for Filbert's r1 changes (F1-F3, N1) | in-review r2, Filbert reviewer: candidate 2c090eefd on code 2a5f3a3a0, base 4f02486d5, manifest c6f4f4c12596; tests R14 R15 + R4/R9/R11 cases, read-only Registry #write() returns; gate passed, K34-K41 killed; Mos checked manifest (mos-0967); revs 2230 (Filbert) and 2231-2233 committed 92cc6011c c539995cf; request comment 29446; P19 337 comment 367; packet 7af166574; lands after B7 2026-10-20T15:40:36Z | Filbert (Opus 5.5, 048f3c63) | row 163 r2 review (D2 rehearse, candidate 2c090eefd, manifest c6f4f4c12596) | approve, comment 29447 rev 2234: 249/249 singles pass; my M1-M6 and N1 mutants all killed (R14, R9, R11, R4, R15, session test) +2026-10-20T15:42:00Z | Sage (Opus 5.5, 1ef1e4f8) | row 163 (#1660) r2 verdict intake | approved by Filbert (comment 29447, rev 2234 committed cd2b377bb); P19 337 comment 367 -> 368; candidate 2c090eefd manifest c6f4f4c12596 waits to land after B7, fresh gate if the base moves