Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
63 lines
5.2 KiB
JavaScript
63 lines
5.2 KiB
JavaScript
// Sage, 2026-10-23: P19 571 (Q195 O2 design rewrite for one-way DOCX conversion). The design landed e8bb13df8 (comment
|
|
// 987) and its ten W build rows are now queue rows. Comments on 571 with the row ids, then marks it done. Sage's own key (bot
|
|
// user 13). The update sends the task as the v1 GET returned it with only done changed; the sync key reads title,
|
|
// description and done back. Rollback is marking it undone. Prints no secret.
|
|
// Usage: node close-571.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 TASK = 571;
|
|
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 TEXT = [
|
|
"Filed. The design's ten W build rows are queue rows as of 18:03Z 2026-10-23, all unassigned, gate owner Sage, brief docs/plans/2026-10-21_q195-writing-editor.md at each row's section: 309 W1, 310 W2, 311 W3a, 312 W3b, 313 W6, 314 W3, 315 W4b, 316 W5, 317 W7, 318 W8 (added revs 3617-3626, owner set to unassigned revs 3627-3636). W4 has no build row; it is the reviewed design itself.",
|
|
"The rows' after links carry the W order from the design: W3b after W3a; W6 after W2, W3b, W1; W3 after W6, W3a, W3b; W4b after W3; W5 after W4b; W7 after W4b and W3; W8 after W3, W4b and W7. Handoffs to C2 rows stay in each brief section. W7's piece says production import stays closed, and the first real import opens only after W8 lands (mos-1470). W8's note carries fenton-0017: connections.test.mjs must test that links in comments and tracked deletions are excluded. Every row is post-10-27, and MVP rows keep slot priority. Sage assigns the seats when each row is briefed.",
|
|
];
|
|
if (/ADDTIME|309 W1, 310 W2, 311 W3a, 312 W3b, 313 W6, 314 W3, 315 W4b, 316 W5, 317 W7, 318 W8 (added revs 3617-3626, owner set to unassigned revs 3627-3636)/.test(JSON.stringify(TEXT))) throw new Error("placeholder left; refusing");
|
|
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(), task: TASK, title: null, 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 task = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
|
|
if (task.status !== 200 || task.json?.project_id !== 19 || !/^Q195 O2 design rewrite for one-way DOCX conversion/.test(task.json?.title ?? "")) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}, title isn't 571's`);
|
|
if (task.json.done) throw new Error(`task ${TASK} is already done; refusing`);
|
|
const before = { title: task.json.title, description: task.json.description };
|
|
rec.title = task.json.title;
|
|
writeFileSync(OUT, "", { flag: "wx" });
|
|
|
|
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: p(...TEXT) }, key);
|
|
rec.comment = { status: c.status, id: c.json?.id ?? null };
|
|
if (c.status >= 300 || !Number.isSafeInteger(c.json?.id)) rec.problems.push(`comment: ${c.status} ${c.json?.message ?? ""}`);
|
|
else {
|
|
const back = await api("GET", `/api/v2/tasks/${TASK}/comments?per_page=50`, undefined, sync);
|
|
const found = list(back.json).find((x) => x.id === c.json.id);
|
|
Object.assign(rec.comment, { readBack: back.status, found: Boolean(found) });
|
|
if (back.status !== 200 || !found) rec.problems.push(`comment read-back: ${back.status}`);
|
|
}
|
|
save();
|
|
if (!rec.problems.length) {
|
|
const full = await api("GET", `/api/v1/tasks/${TASK}`, undefined, key);
|
|
if (full.status !== 200 || full.json?.id !== TASK) rec.problems.push(`v1 get: ${full.status}`);
|
|
else {
|
|
const u = await api("POST", `/api/v1/tasks/${TASK}`, { ...full.json, done: true }, key);
|
|
rec.update = { status: u.status, inverse: `mark undone: POST /api/v1/tasks/${TASK} done false (sage key)` };
|
|
const back = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
|
|
Object.assign(rec.update, { readBack: back.status, done: back.json?.done, titleSame: back.json?.title === before.title, descriptionSame: back.json?.description === before.description });
|
|
if (u.status >= 300 || back.status !== 200 || back.json?.done !== true || !rec.update.titleSame || !rec.update.descriptionSame) rec.problems.push("update or read-back differs");
|
|
}
|
|
}
|
|
save();
|
|
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
|
|
if (rec.problems.length) process.exit(2);
|