decisions, vikunja-setup: 113 (row 61 field table follows the code, edited in the row); #154 comment; #218 close-verdict follow-up
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"at": "2026-10-12T18:17:41.624Z",
|
||||
"project": 19,
|
||||
"title": "Decide whether task.close checks the cited review verdict (decision 113)",
|
||||
"problems": [],
|
||||
"create": 201,
|
||||
"id": 218,
|
||||
"index": 69,
|
||||
"inverse": "mark done: POST /api/v1/tasks/218 done true; delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"createdBy": "bot-mosaic-stack-sage"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"at": "2026-10-12T18:16:37.475Z",
|
||||
"task": 154,
|
||||
"problems": [],
|
||||
"title": "M1 brief and queue row: chief-of-staff role (decision 104)",
|
||||
"status": 201,
|
||||
"id": 31,
|
||||
"readBack": 200,
|
||||
"found": true,
|
||||
"author": "bot-mosaic-stack-sage"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Sage, 2026-10-12: Mos sage-0089. Comments on #154 (the M1 brief) that the brief and queue row 84 exist.
|
||||
// Sage's own key (bot user 13). The sync key reads the task title first and the comment back. No state
|
||||
// change to the task. Prints no secret.
|
||||
// Usage: node comment-m1-brief.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 = 154;
|
||||
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 = "Done: the M1 brief is docs/plans/2026-10-12_m1-chief-of-staff-role.md (commit ff61135e), on BRIEF-TEMPLATE.md from M1-draft.md and lead decision 104. Queue row 84, Gitea #1578, briefed at queue rev 671 (95cb51c3). Owner Rocko after row 77; reviewers Darkwing and Filbert; gate Sage. The instance name stays with Jason (#150), so the row documents the entry with a placeholder.";
|
||||
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, 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);
|
||||
rec.title = task.json?.title ?? null;
|
||||
if (task.status !== 200 || task.json?.project_id !== 19) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}`);
|
||||
writeFileSync(OUT, "", { flag: "wx" });
|
||||
const r = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: p(TEXT) }, key);
|
||||
Object.assign(rec, { status: r.status, id: r.json?.id ?? null });
|
||||
if (r.status >= 300 || !Number.isSafeInteger(r.json?.id)) rec.problems.push(`comment: ${r.status} ${r.json?.message ?? ""}`);
|
||||
else {
|
||||
const back = await api("GET", `/api/v2/tasks/${TASK}/comments?per_page=50`, undefined, sync);
|
||||
const found = list(back.json).find((c) => c.id === r.json.id);
|
||||
Object.assign(rec, { readBack: back.status, found: Boolean(found), author: found?.author?.username ?? null });
|
||||
if (back.status !== 200 || !found) rec.problems.push(`read-back: ${back.status}`);
|
||||
}
|
||||
save();
|
||||
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
|
||||
if (rec.problems.length) process.exit(2);
|
||||
@@ -0,0 +1,57 @@
|
||||
// Sage, 2026-10-12: lead decision 113. Files one task in project 19: task.close records the cited review
|
||||
// verdict without checking it. Sage's own key (bot user 13). Refuses if the title exists. The sync key
|
||||
// reads it back. No labels, no assignees, no bucket moves. Rollback is marking it done; deletion is
|
||||
// Jason's or ops-01's. Prints no secret.
|
||||
// Usage: node file-close-verdict.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 TITLE = "Decide whether task.close checks the cited review verdict (decision 113)";
|
||||
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 DESCRIPTION = p(
|
||||
"Darkwing, on queue row 61 (Gitea #1550): task.close takes the review verdict as a citation and records it without checking it (packages/tasks/src/verbs.mjs:334). The slice 1 field table said done is written by pm 'after a review verdict'.",
|
||||
"Lead decision 113 corrects the table to 'pm, citing a review verdict (recorded, not checked)' inside row 61 and leaves the behaviour alone. Open question for a later row: should close verify that the cited verdict exists and approves (for example against the queue review record or the Gitea comment), and refuse otherwise? Sage decides when slice 1 acceptance or a review shows a need.",
|
||||
);
|
||||
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, title: TITLE, 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 clash = (await existing()).filter((t) => t.title === TITLE);
|
||||
if (clash.length) throw new Error(`exists: #${clash[0].id}; refusing`);
|
||||
writeFileSync(OUT, "", { flag: "wx" });
|
||||
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, { title: TITLE, description: DESCRIPTION }, key);
|
||||
rec.create = r.status;
|
||||
if (r.status >= 300 || !Number.isSafeInteger(r.json?.id)) {
|
||||
rec.problems.push(`create: ${r.status} ${r.json?.code ?? ""} ${r.json?.message ?? ""}`);
|
||||
} else {
|
||||
Object.assign(rec, { id: r.json.id, index: r.json.index, inverse: `mark done: POST /api/v1/tasks/${r.json.id} done true; delete is Jason's or ops-01's` });
|
||||
const back = await api("GET", `/api/v2/tasks/${r.json.id}`, undefined, sync);
|
||||
Object.assign(rec, { readBack: back.status, createdBy: back.json?.created_by?.username });
|
||||
if (back.status !== 200 || back.json?.title !== TITLE || back.json?.project_id !== PROJECT || back.json?.description !== DESCRIPTION) rec.problems.push("read-back differs");
|
||||
}
|
||||
save();
|
||||
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
|
||||
if (rec.problems.length) process.exit(2);
|
||||
Reference in New Issue
Block a user