sessions, vikunja-setup: restarted rows filed as #207-#217, row 77 comments on #146 #147 #155

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-12 13:11:23 -05:00
co-authored by Claude Opus 5.5
parent e65af8358f
commit 27a1a2bb16
3 changed files with 212 additions and 0 deletions
@@ -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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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);
+1
View File
@@ -757,3 +757,4 @@ are never rewritten or removed; corrections are new entries.
2026-10-12T17:46:46Z | sage | Vikunja #198 launchpad-pm key | Mos sage-0085: svc-mosaic-stack minted bot-launchpad-pm (user 12) and key 12 to mosaic-stack/launchpad-pm-vikunja.token (0600, 43 bytes, expires 2027-01-07) with mint-bot.mjs, the mint-mos.mjs pattern generalized; scopes identical to key 11 (Q61a cut); /tasks/198 403 and 0 projects until ops-01 adds user 12 to team 1 (Mos routes it); seat list to Mos
2026-10-12T17:47:42Z | sage | Vikunja #198 sage and ops-01 keys | Mos sage-0086 (rule: a seat that files its own work on tasks.woltje.com gets a key): mint-bot.mjs minted bot-mosaic-stack-sage (user 13, key 13) and bot-mosaic-stack-ops-01 (user 14, key 14) to mosaic-stack/sage-vikunja.token and ops-01-vikunja.token (0600, 43 bytes, expires 2027-01-07), scopes identical to key 11; 0 projects and /tasks/198 403 until ops-01 adds users 12-14 to team 1 (ops01-0098 for 12, Mos go for 13 and 14); Darkwing, Dewey, Filbert, Researcher and Rocko get none, Sage files for them
2026-10-12T18:00:47Z | sage | gap table vs TARGET.md, Vikunja #206 | Mos sage-0088, fact-finding only: eleven TARGET items rated exists/partial/missing with file:line evidence and covering rows or plan items; filed as P19 #206 with Sage's own key (user 13), read back with sync; deployment and Vikunja exist, WebUI, TUI and most settings partial, system-level branch strategy and CLIProxyAPI missing
2026-10-12T18:11:23Z | sage | decision 111, row 77 re-brief, decision 94 holds lifted | Mos sage-0089/0090/0091: decision 111 records Q111+Q112 (dates don't gate, advance without Jason except hard-rule waits and strategy, Q97 order stands); decision 112 folds Vikunja #146 pinned copy, #147 uid rule (header, not a Unix user) and #155 into row 77 with brief docs/plans/2026-10-12_console-pinned-copy.md, Darkwing added as reviewer, briefed at rev 641; rows 49, 61, 63-66, 69-71 back to briefed and 80, 81 to queued (rev 642-664, e65af835); P19 #207-#217 filed for those rows with the sage key and comments on #146, #147, #155, all read back with sync