Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
{
|
||||
"at": "2026-10-12T18:11:14.268Z",
|
||||
"project": 19,
|
||||
"created": [
|
||||
{
|
||||
"title": "Row 49: Bus comment cursor across restarts",
|
||||
"id": 207,
|
||||
"index": 58,
|
||||
"inverse": "mark done: POST /api/v1/tasks/207 done true; delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"createdBy": "bot-mosaic-stack-sage"
|
||||
},
|
||||
{
|
||||
"title": "Row 61: Task field-writer table as data",
|
||||
"id": 208,
|
||||
"index": 59,
|
||||
"inverse": "mark done: POST /api/v1/tasks/208 done true; delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"createdBy": "bot-mosaic-stack-sage"
|
||||
},
|
||||
{
|
||||
"title": "Row 63: Multi-project verbs: task verbs across the tree",
|
||||
"id": 209,
|
||||
"index": 60,
|
||||
"inverse": "mark done: POST /api/v1/tasks/209 done true; delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"createdBy": "bot-mosaic-stack-sage"
|
||||
},
|
||||
{
|
||||
"title": "Row 64: Managed sessions get their role's skills on both harnesses",
|
||||
"id": 210,
|
||||
"index": 61,
|
||||
"inverse": "mark done: POST /api/v1/tasks/210 done true; delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"createdBy": "bot-mosaic-stack-sage"
|
||||
},
|
||||
{
|
||||
"title": "Row 65: Model routing table: sessions resolve harness, model and thinking from one table",
|
||||
"id": 211,
|
||||
"index": 62,
|
||||
"inverse": "mark done: POST /api/v1/tasks/211 done true; delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"createdBy": "bot-mosaic-stack-sage"
|
||||
},
|
||||
{
|
||||
"title": "Row 66: Managed sessions run the unslop check on both harnesses",
|
||||
"id": 212,
|
||||
"index": 63,
|
||||
"inverse": "mark done: POST /api/v1/tasks/212 done true; delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"createdBy": "bot-mosaic-stack-sage"
|
||||
},
|
||||
{
|
||||
"title": "Row 69: Terminal app follow-up: narrow-terminal guard test, hidden-box way out and width tests",
|
||||
"id": 213,
|
||||
"index": 64,
|
||||
"inverse": "mark done: POST /api/v1/tasks/213 done true; delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"createdBy": "bot-mosaic-stack-sage"
|
||||
},
|
||||
{
|
||||
"title": "Row 70: Docs say mosaic on PATH is the estate CLI; after Q81 it can be this one",
|
||||
"id": 214,
|
||||
"index": 65,
|
||||
"inverse": "mark done: POST /api/v1/tasks/214 done true; delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"createdBy": "bot-mosaic-stack-sage"
|
||||
},
|
||||
{
|
||||
"title": "Row 71: Tree read follow-up: confirm-read tests and the fake's view 404",
|
||||
"id": 215,
|
||||
"index": 66,
|
||||
"inverse": "mark done: POST /api/v1/tasks/215 done true; delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"createdBy": "bot-mosaic-stack-sage"
|
||||
},
|
||||
{
|
||||
"title": "Row 80: Bus: list messages stranded by a holder change (row 59 review Note B)",
|
||||
"id": 216,
|
||||
"index": 67,
|
||||
"inverse": "mark done: POST /api/v1/tasks/216 done true; delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"createdBy": "bot-mosaic-stack-sage"
|
||||
},
|
||||
{
|
||||
"title": "Row 81: Console shows the reply to a web send",
|
||||
"id": 217,
|
||||
"index": 68,
|
||||
"inverse": "mark done: POST /api/v1/tasks/217 done true; delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"createdBy": "bot-mosaic-stack-sage"
|
||||
}
|
||||
],
|
||||
"comments": [
|
||||
{
|
||||
"task": 146,
|
||||
"status": 201,
|
||||
"id": 25,
|
||||
"readBack": 200,
|
||||
"found": true
|
||||
},
|
||||
{
|
||||
"task": 147,
|
||||
"status": 201,
|
||||
"id": 26,
|
||||
"readBack": 200,
|
||||
"found": true
|
||||
},
|
||||
{
|
||||
"task": 155,
|
||||
"status": 201,
|
||||
"id": 27,
|
||||
"readBack": 200,
|
||||
"found": true
|
||||
}
|
||||
],
|
||||
"problems": []
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Sage, 2026-10-12: Mos sage-0091 and lead decision 111. Files one task in project 19 for each queue row
|
||||
// restarted when decision 94's hold ended, and comments on #146, #147 and #155 that row 77 now covers them
|
||||
// (decision 112). Sage's own key (bot user 13). Refuses if any title already exists. The sync key reads
|
||||
// each task back. No labels, no assignees, no bucket moves. Rollback is marking a task done; deletion is
|
||||
// Jason's or ops-01's. Prints no secret.
|
||||
// Usage: node file-restarted-rows.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 ROWS = [
|
||||
[49, "Bus comment cursor across restarts", "sage", 1535, "darkwing, filbert", "row 41 (done)"],
|
||||
[61, "Task field-writer table as data", "darkwing", 1550, "dewey, filbert", "row 41 (done)"],
|
||||
[63, "Multi-project verbs: task verbs across the tree", "darkwing", 1556, "dewey, filbert", "row 62 (done)"],
|
||||
[64, "Managed sessions get their role's skills on both harnesses", "sage", 1557, "darkwing, filbert", "row 49"],
|
||||
[65, "Model routing table: sessions resolve harness, model and thinking from one table", "sage", 1558, "darkwing, dewey", "row 64"],
|
||||
[66, "Managed sessions run the unslop check on both harnesses", "sage", 1559, "dewey, filbert", "row 65"],
|
||||
[69, "Terminal app follow-up: narrow-terminal guard test, hidden-box way out and width tests", "darkwing", 1562, "dewey, filbert", "row 55 (done)"],
|
||||
[70, "Docs say mosaic on PATH is the estate CLI; after Q81 it can be this one", "sage", 1564, "filbert", "none"],
|
||||
[71, "Tree read follow-up: confirm-read tests and the fake's view 404", "darkwing", 1565, "dewey, filbert", "none"],
|
||||
[80, "Bus: list messages stranded by a holder change (row 59 review Note B)", "darkwing", 1574, "filbert", "row 59 (done)"],
|
||||
[81, "Console shows the reply to a web send", "dewey", 1575, "darkwing", "row 76 (done); the first piece of M2"],
|
||||
];
|
||||
const TASKS = ROWS.map(([id, piece, owner, issue, reviewers, after]) => ({
|
||||
title: `Row ${id}: ${piece}`,
|
||||
description: p(
|
||||
`Queue row ${id}, Gitea #${issue} on mosaicstack/stack. Owner ${owner}; reviewers ${reviewers}; after ${after}.`,
|
||||
"Held by lead decision 94 until the cutover, which went live at 2026-10-12T17:21Z. Restarted under lead decision 111 (Q111, Q112: no date gating) and Mos sage-0091.",
|
||||
"The queue row and its Gitea issue stay the review and landing record (decision 110). This task tracks the work item.",
|
||||
),
|
||||
}));
|
||||
const COMMENTS = [
|
||||
[146, "Covered by queue row 77 (Gitea #1571, owner Rocko, reviewers Darkwing and Filbert), brief docs/plans/2026-10-12_console-pinned-copy.md, lead decision 112: the unit runs from a read-only git archive copy of a refactor commit, with a copy verb and install --copy as the deliberate update step. Mos does the one reinstall after the row lands."],
|
||||
[147, "Covered by queue row 77 (lead decision 112). Decision 101 means the X-authentik-uid header in place of X-authentik-username, not a dedicated Unix user; a Unix user would break decision 62's ancestry proof and needs sudo (Q18). The row adds the check that the allowed user matches ^[0-9a-f]{64}$ with that header. ops-01 reads user pk 5's uid without writing; Mos uses it in the same reinstall."],
|
||||
[155, "Done: row 77's brief is now docs/plans/2026-10-12_console-pinned-copy.md (commit 274f6b3d), which also covers #146 and #147 (lead decision 112). Row 77 re-pinned and briefed at queue rev 641 (e65af835)."],
|
||||
];
|
||||
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: [], 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);
|
||||
};
|
||||
const clash = (await existing()).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 r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, { title: t.title, description: t.description }, 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; 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, createdBy: back.json?.created_by?.username });
|
||||
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.description !== t.description) rec.problems.push(`${t.title} read-back differs`);
|
||||
}
|
||||
if (!rec.problems.length) for (const [id, text] of COMMENTS) {
|
||||
const comment = p(text);
|
||||
const r = await api("PUT", `/api/v1/tasks/${id}/comments`, { comment }, key);
|
||||
rec.comments.push({ task: id, status: r.status, id: r.json?.id ?? null });
|
||||
if (r.status >= 300 || !Number.isSafeInteger(r.json?.id)) { rec.problems.push(`comment #${id}: ${r.status} ${r.json?.message ?? ""}`); break; }
|
||||
const back = await api("GET", `/api/v2/tasks/${id}/comments?per_page=50`, undefined, sync);
|
||||
const found = list(back.json).find((c) => c.id === r.json.id);
|
||||
Object.assign(rec.comments.at(-1), { readBack: back.status, found: Boolean(found) });
|
||||
if (back.status !== 200 || !found) rec.problems.push(`comment #${id} read-back: ${back.status}`);
|
||||
}
|
||||
save();
|
||||
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
|
||||
if (rec.problems.length) process.exit(2);
|
||||
Reference in New Issue
Block a user