sage: Vikunja scripts and read-back records 2026-10-13 to 10-20 (decision 110, mos-0882)

102 files under agents/sage/work/vikunja-setup/: the comment, close and file scripts Sage ran on P19 and their
output records, including P19 386 comment 350 for the mos-0928 Part A window (rows 177 and 180-185). Each script
refuses to write a record that holds a key; a scan for both key values found none.
This commit is contained in:
2026-10-20 03:32:46 -05:00
parent 21aa1cab0b
commit e24a13ad87
102 changed files with 4044 additions and 0 deletions
@@ -0,0 +1,17 @@
{
"at": "2026-10-13T22:41:14.615Z",
"project": 19,
"created": [
{
"title": "test-release: healthy activation failed once in the row 104 gate, not reproduced",
"id": 258,
"index": 101,
"inverse": "mark done: POST /api/v1/tasks/258 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
}
],
"problems": []
}
@@ -0,0 +1,27 @@
{
"at": "2026-10-14T01:42:21.222Z",
"project": 19,
"created": [
{
"title": "webui: redirect sign-out browser test timed out once in the row 110 gate, not reproduced",
"id": 262,
"index": 105,
"inverse": "mark done: POST /api/v1/tasks/262 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
},
{
"title": "test-task: forked child did not recall the ancestor code word once in the row 110 gate, not reproduced",
"id": 263,
"index": 106,
"inverse": "mark done: POST /api/v1/tasks/263 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
}
],
"problems": []
}
@@ -0,0 +1,11 @@
{
"at": "2026-10-15T22:05:21.048Z",
"task": 314,
"problems": [],
"title": "Q167: make the Stack run from a north star (T3 seats build, Jason tests)",
"status": 201,
"id": 223,
"readBack": 200,
"found": true,
"author": "bot-mosaic-stack-sage"
}
@@ -0,0 +1,11 @@
{
"at": "2026-10-15T22:35:15.427Z",
"task": 314,
"problems": [],
"title": "Q167: make the Stack run from a north star (T3 seats build, Jason tests)",
"status": 201,
"id": 225,
"readBack": 200,
"found": true,
"author": "bot-mosaic-stack-sage"
}
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T03:24:53.587Z",
"task": 386,
"comment": {
"status": 201,
"id": 335,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T02:48:10.586Z",
"task": 386,
"comment": {
"status": 201,
"id": 332,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T03:33:19.113Z",
"task": 386,
"comment": {
"status": 201,
"id": 336,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T03:56:15.288Z",
"task": 386,
"comment": {
"status": 201,
"id": 337,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T02:32:57.469Z",
"task": 390,
"comment": {
"status": 201,
"id": 331,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T02:32:57.161Z",
"task": 391,
"comment": {
"status": 201,
"id": 330,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,17 @@
{
"at": "2026-10-18T21:30:18.554Z",
"project": 19,
"created": [
{
"title": "webui test: board-held.test.mjs:163 sign-out wait times out under suite load (twice on 2026-10-18)",
"id": 366,
"index": 206,
"inverse": "mark done: POST /api/v1/tasks/366 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
}
],
"problems": []
}
@@ -0,0 +1,9 @@
{
"at": "2026-10-19T19:01:05.935Z",
"task": 366,
"title": "webui test: board-held.test.mjs:163 sign-out wait times out under suite load (twice on 2026-10-18)",
"status": 201,
"id": 279,
"readBack": 200,
"found": true
}
@@ -0,0 +1,9 @@
{
"at": "2026-10-19T19:06:15.058Z",
"task": 366,
"title": "webui test: board-held.test.mjs:163 sign-out wait times out under suite load (twice on 2026-10-18)",
"status": 201,
"id": 280,
"readBack": 200,
"found": true
}
@@ -0,0 +1,63 @@
// Sage, 2026-10-19: row 134 landed (3a600cf5b on refactor, #1669). Comments on P19 308
// with the landing, 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-379.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 = 308;
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 TEXT = [
"Fixed by row 134, landed as 3a600cf5b on refactor and pushed (078518e4e..a85e573d4). Researcher built it, Filbert approved candidate manifest 8977d907 in Gitea 29193, and #1669 is closed (29201). Filbert's README follow-up is a85e573d4.",
"A role holding organization.change now reads the organization file's current revision itself: the bus verb organization.revision and the chief of staff's organization_revision tool. It answers the revision and nothing else, never spends the grant and writes no receipt. limits stays human-only.",
"Sage's landing gate passed on 3a600cf5b on its second run. The first run stopped on the board-held flake (P19 366, row 172), which this row doesn't touch.",
"Live state is unchanged. The live stack runs from a pinned copy, so this reaches it only through a later re-pin.",
];
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 || !/organization revision/i.test(task.json?.title ?? "")) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}, title doesn't name the organization revision`);
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);
@@ -0,0 +1,62 @@
// Sage, 2026-10-18: Jason's ruling on P19 338 (repo license), relayed by Mos in mos-0787. Comments on
// #338 with the ruling and 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-338.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 = 338;
const TITLE = "Needs Jason: repo license for mosaicstack/stack (row 154, decision 123)";
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 TEXT = [
"Jason ruled, relayed by Mos in mos-0787 (2026-10-18): \"Keep private for now\".",
"So mosaicstack/stack stays private and UNLICENSED. The license notices that row 154 imported with its third-party editor code stay in the import directory beside that code. Nothing else changes.",
"If the repository is ever made public or given a license, that is a new question for Jason and gets a new task.",
];
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);
if (task.status !== 200 || task.json?.project_id !== 19 || task.json?.title !== TITLE) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}, title differs or missing`);
if (task.json.done) throw new Error(`task ${TASK} is already done; refusing`);
const before = { title: task.json.title, description: task.json.description };
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);
@@ -0,0 +1,63 @@
// Sage, 2026-10-19: row 172 landed (be10e0c83 on refactor, #1670). Comments on P19 366
// with the landing, 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-366.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 = 366;
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 TEXT = [
"Fixed by row 172, landed as be10e0c83 on refactor and pushed (0ed9412fc..c24fe5d0f). Sage built it, Darkwing approved candidate manifest 278b0eff in Gitea 29207, and #1670 is closed (29211).",
"The root cause was in the product, not the test. The Board page's Seen click returned without a word while a Board read was in flight, so no /api/seen went out and no sign-out answer came back. The click now waits for the read, then posts from the fresh row; a second click while one waits or posts is dropped. No timeout changed (ruling mos-0858).",
"The same row fixed the row 165 test in instances-browser.test.mjs: it reloaded from an evaluate and its next wait raced the reload. It now navigates to the page with a new query, which waits for the load.",
"Acceptance: 10 webui runs in a row green on the candidate, 239 pass each, and the landing gate passed. Live state is unchanged. The live stack runs from a pinned copy, so this reaches it only through a later re-pin.",
];
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 || !/board-held\.test\.mjs:163 sign-out/.test(task.json?.title ?? "")) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}, title doesn't name the board-held sign-out wait`);
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);
@@ -0,0 +1,63 @@
// Sage, 2026-10-19: row 168 landed (efb51169d, #1665 closed). Comments on P19 372 with the landing and the
// corrected 10-11 totals, 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-372.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 = 372;
const TITLE = "Ledger: a #N for another tracker in a commit subject is counted as a stack issue (row 7, #1508)";
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 TEXT = [
"Fixed by row 168, landed as efb51169d on refactor and pushed. Gitea #1665 is closed (landing comment 29129). Dewey approved the candidate manifest in 29121.",
"The ledger now skips a #N right after Vikunja, P19, SetSpark or Launchpad, and drops numbers below #1495 unless --first-issue says otherwise.",
"The 10-11 week rerun is posted on #1508 as 29126: 97 table rows, none below #1495, 557 commits, 5.3 follow-ups per issue, matching the recompute in 29089.",
"Dewey's two non-blocking notes and a webui test flake from the landing gate are filed as their own tasks.",
];
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);
if (task.status !== 200 || task.json?.project_id !== 19 || task.json?.title !== TITLE) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}, title differs or missing`);
if (task.json.done) throw new Error(`task ${TASK} is already done; refusing`);
const before = { title: task.json.title, description: task.json.description };
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);
@@ -0,0 +1,63 @@
// Sage, 2026-10-19: row 171 landed (078518e4e, pushed in d58c9c5cd..078518e4e, #1668). Comments on P19 379
// with the landing, 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-379.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 = 379;
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 TEXT = [
"Fixed by row 171, landed as 078518e4e on refactor and pushed (d58c9c5cd..078518e4e). Rocko built it, Dewey approved round 2's candidate manifest c498ca89 in Gitea 29191, and #1668 is closed (29197).",
"A thread project whose name matches a business-file repo is now refused project-name-is-repo unless --repo names that same repo. The bus, the CLI and the Console all apply the check, and the CLI's refusal text says what to pass.",
"Sage's landing gate passed on 9a29febb6, the commit rebuilt on d58c9c5cd, and git range-diff shows it equal to 078518e4e. The board-held flake (P19 366) didn't trip this run.",
"Live state is unchanged. The live stack runs from a pinned copy, so this reaches it only through a later re-pin.",
];
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 || !/registries/i.test(task.json?.title ?? "")) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}, title doesn't name the two registries`);
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);
@@ -0,0 +1,63 @@
// Sage, 2026-10-19: row 170 landed (cb43dcb45, pushed in 228aeb503..d58c9c5cd, #1667). Comments on P19 380
// with the landing, 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-380.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 = 380;
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 TEXT = [
"Fixed by row 170, landed as cb43dcb45 on refactor and pushed (228aeb503..d58c9c5cd). Dewey built it, Filbert approved the candidate manifest in Gitea 29184, and #1667 is closed.",
"A tester message to a role nobody runs is now refused tester-target-not-running before the bus stores it, and mosaic talk --tester exits 3 on that refusal. Non-tester sessions are unchanged.",
"Sage's landing gate passed on the rebased tree (run 4). Runs 1 and 3 stopped on the webui board-held flake, which is P19 366 (comments 279 and 280), not this change.",
"Filbert's non-blocking follow-up from 29184 is recorded in the row 170 note.",
];
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 || !/tester/i.test(task.json?.title ?? "")) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}, title doesn't name the tester refusal`);
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);
@@ -0,0 +1,61 @@
// Sage, 2026-10-20: merged B+C window W5 to W9 passed (plan draft 9 line 135). Comments on P19 382
// with the window result, 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-382.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 = 382;
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 TEXT = [
"Fixed on the live stack by the merged B+C window (2026-10-20). Row 169 built the two-phase PM start (#1666). W5 activated 89af38a83 with --pm-after-harness (20261020T013708Z-89af38a83afa), and the bus came up without its PM. W6: the tester set plugins.harness to [claude-code]. W7 pm-on (20261020T014103Z-89af38a83afa) passed its gate and launched chief rc78c9700c8e9.",
"W5 to W9 passed, so this closes as the window plan says (draft 9, line 135). The full summary is P19 386 comment 324, and the STATE report is agents/darkwing/work/window-merged-bc/STATE.md (sha256 7be42b6bcab5657d).",
];
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 || !/c216f47db/.test(task.json?.title ?? "")) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}, title doesn't name c216f47db`);
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);
@@ -0,0 +1,20 @@
{
"at": "2026-10-20T02:24:07.985Z",
"task": 394,
"title": "mosaic-stack: carry-past-row64.md runs skill.sh from REPO pinned to NEW, not the stack copy",
"problems": [],
"comment": {
"status": 201,
"id": 329,
"readBack": 200,
"found": true
},
"update": {
"status": 200,
"inverse": "mark undone: POST /api/v1/tasks/394 done false (sage key)",
"readBack": 200,
"done": true,
"titleSame": true,
"descriptionSame": true
}
}
@@ -0,0 +1,61 @@
// Sage, 2026-10-20: #394 carry doc fix committed as 88f3253d9 (mos-0924). Comments on P19 394
// with the fix, 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-394.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 = 394;
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 TEXT = [
"Fixed in 88f3253d9 on refactor, not yet pushed. carry-past-row64.md now runs the skill carry from REPO held to the pinned sha N. Before the loop, git diff --stat N and git status --porcelain --ignored on scripts/skill.sh, scripts/common.sh and the seven skills dirs must print nothing. After it, each skills-enabled/<s> file list must equal git ls-tree N skills/<s>, and each file's sha256 must equal git show N:skills/<s>/<file>. The rollback also runs from REPO. A dated correction paragraph cites mos-0914, mos-0915, Darkwing's W8 hold and window deviation 1.",
"The same commit amends the merged B+C plan. Line 222 now names what the tester Settings page shows (mos-0920, mos-0923). The W8 row now matches the carry doc. Mos checked the diff in mos-0924. carry-past-row64.md sha256 6799542cb23aba4c, repin-merged-bc.md 99f86c078d01663f.",
];
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 || !/carry-past-row64/.test(task.json?.title ?? "")) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}, title doesn't name carry-past-row64`);
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);
@@ -0,0 +1,63 @@
// Sage, 2026-10-20: row 185 landed (490e2771d, pushed in b5d662cab..2ea05700b, #1683). Comments on P19 403
// with the landing, 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-403.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 = 403;
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 TEXT = [
"Row 185 landed as 490e2771d on refactor (author Darkwing) and was pushed in b5d662cab..2ea05700b. Filbert approved round 1 in Gitea 29404, and #1683 is closed (29405). Patch-id 774cd23186d5 matches the approved candidate, manifest bf87dbba.",
"Every stack-release.sh activate now backs up the stopped store under its activation id before any migration. The in-run restore stays migration-only, and activate --plan prints a backup line, or backup: none when there is no store file.",
"Sage's gate on b5d662cab plus the patch passed: node 2518/0, scripts 422/0, queue 156. Filbert's two notes are doc follow-ups: the plan's backup line also prints on runs it says will roll back first, and TOOLS.md step 3 doesn't list the line.",
"Not live. The stack still runs copy 89af38a83afa. mos-0928 Part A re-pins to 490e2771d, and its plan (draft 2, 2ea05700b) goes to Mos for the check before W0.",
];
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 || !/^Row 185:/.test(task.json?.title ?? "")) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}, title isn't row 185'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);
@@ -0,0 +1,48 @@
// Sage, 2026-10-15: Mos #314 / Q167. Comments on #314 with the plan sent to Mos as sage-0376.
// 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-314-plan.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 = 314;
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 TEXT = [
"Plan sent to Mos as sage-0376, 2026-10-15T22:05Z. Not started: it waits on Mos for Q4 (agents land into the business working repo, not canonical refactor, recommended) and Q5 (Q167 covers shrinking GATED_ONLY to the hard-waits list).",
"Root causes. S1: an approval is consumable only by the run that raised it (packages/bus/src/broker.mjs:600, supersede :1044). S2: git.merge.protected is in GATED and GATED_ONLY, so no role can hold it. S4: anything not granted classifies gated and routes to the human (broker.mjs:447, :1036). S8: talk prints and marks read every inbox message while it waits (packages/cli/src/cli.mjs:240-244).",
"Rows 135-143. 135 Sage: PRD 1.5 under Q167, after 297. 136 Dewey: S1, approvals bind to decision and role instance, consumed once, no schema step. 137 Darkwing: S2, a land op on the host git socket using land-work checks and gate. 138 Rocko: S3 and S5, versioned specs, schema 3f. 139 Dewey: S4, spec-covered decisions route to the arbiter, hard waits stay with Jason. 140 Researcher: S6, pm staff request to chief. 141 Darkwing: S7, instance names, folds in 623b1665, schema 3g. 142 Researcher: S8. 143 Rocko: S9 and S10. Filbert reviews unless the row says Dewey. Rows 124-126, 128 and 129 wait behind these.",
"Schema window: code-only rows ride any re-pin after the 3e window. 138 and 141 each get their own window, one schema step per window, with the gap rule for land-work.",
"Acceptance: Jason sets a north star through the assistant (a mosaic version command), confirms spec v1, watches pm, chief, a coder and a reviewer produce and land it with an empty inbox, survives a host restart, changes the spec once without reopening settled decisions, and gets a block back on a proposal that breaks the spec. The script lands as docs/acceptance/314.md in row 135.",
];
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,51 @@
// Sage, 2026-10-15: Mos #314 / Q167. Comments on #314 with the revised row list (sage-0391, accepted by Mos,
// Jason's go relayed 22:31Z) and the pm-launch fix row 146. 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-314-rows-r2.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 = 314;
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 TEXT = [
"Revised row list, 2026-10-15T22:35Z. It replaces the list in comment 223 (sage-0376). Jason added text to Q167: specs visible and editable by the user in each project, git history, a standard project layout with JSON and templates, markdown with a WYSIWYG editor, and coder, code review, security review and merge gate roles. Mos accepted the list as sage-0391, and Jason said: \"Your decisions look good. Proceed.\"",
"Rulings in it. Each project is a separate local git repo, at the path in the business project entry, default <dataRoot>/projects/<business>/<project>/. The Console shows its files. Agents read spec/north-star.md read-only and propose changes to Jason by message. Agents write docs, requirements and decisions as agent-authored commits through row 147's validators. The editor may be one vendored, pinned library with a permissive license recorded, a sha256 in the repo, no runtime fetch, served from public/ under the current CSP. Security review comes from row 149's role once it exists, and from Filbert and Dewey until then. The library and its size are named at row 148's brief, under decision 123.",
"146 (new, first) Dewey, Filbert reviews: the pm-launch fault from task 205 comment 224. A human talk to a role with no live run waited forever. When a message is routed to a role with no live claim, the bus tells the launch.by instance (chief) once per waiting message, and chief launches the role or replies. talk says so. If chief itself isn't running, talk says that and prints the command that brings chief back, or the bus starts chief within the launch rules (Dewey's call).",
"147 (new, A) Rocko, Filbert reviews: project layout. project.json, spec/north-star.md, docs/, requirements/, decisions/ and templates/, one JSON schema and template per record kind, mosaic project init with git init, and validators.",
"148 (new, B) Researcher, Filbert and Dewey review: Console WYSIWYG editor for the spec and docs. Each save is a human-authored local git commit through the Console send proof. Needs 147.",
"149 (new, C) Dewey, Filbert reviews: coding-workflow roles. coder and reviewer stay; new security-reviewer (read and verdict tools only) and merge-gate (holds the 137 land op only). Every role contract gets the proactive line. After 137 and 139.",
"Rescoped. 135: PRD 1.5 adds the layout, specs as files, the editor, the four workflow roles and proactive contracts. 137 (Darkwing): main moves only by land, root HEAD goes to refs/heads/root; land-work's gate for a mosaic-stack checkout, else gate-missing; the merge-gate role gets the land op through 149. 138 (Rocko, Dewey reviews, after 147): the spec is a markdown file in the project git; decisions gain spec_path and spec_commit (schema 3f); a human-authored commit is the confirmation; agents can't commit the spec; S5 stays. 139 (Dewey): coverage means citing the current spec commit, plus the name-setting grant; needs 135, 136 and 138. 141 (Darkwing): table instance_names plus messages.to_name, human-only via the CLI until 139. 143 (Researcher after 142, Dewey reviews): agents review Jason's spec commits and the assistant summarizes.",
"Unchanged: 136 (Dewey, in review, Filbert approved round 1), 140 (Researcher, pm staff request), 142 (Researcher, in progress). Order: 146, then 147, then 138 and 148, with the others as their inputs land.",
];
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,45 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row 175
// landing (98d29769e) and the row 176 brief amendment.
// Copy for mos-0888: the early plan read of the merged window. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Decision (Mos, mos-0888): early read of the merged B+C window plan, draft 1. Q1: --migrate runs without Jason under Q173, which lifts decision 96 conditions 1 to 3 (who runs it, Jason present, the --events wait). Condition 4, no pruning, stands. Q2: the row 176 stack-release change is approved, and Dewey's review checks that tester-port-target reads the target copy, not the repo. Q3 and Q4 approved with two fixes.",
"F1: the S1/S2 store snapshot hashes usage_records, instance_names and threads over explicit column lists in seq order, and Mos reads the script before the window. F2: the window's coder start runs only 'List the skills in your prompt, then stop', with no repo write, push, PR, issue or outward message. F3: every live step waits for Mos to verify the one before.",
"Draft 2 (sha256 6cf815db3e4ecbb8) has these. Row 176 is being built by Rocko. Mos runs the formal plan check after 176 lands. Live state is unchanged; the live copy is still 5fa9cc8f2.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T00:16:23.664Z",
"task": 386,
"comment": {
"status": 201,
"id": 307,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row
// mos-0929 rulings on the mos-0928 plan. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-mos0929.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 = 386;
const SIG = "Posted 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 BODY = p(
"Decision (Mos, mos-0929, 2026-10-20), recorded by Sage: on the mos-0928 plan (agents/sage/work/window-plan/repin-rows-177-185.md, sha256 745603fe2292 on 52ce57dfe), Mos confirmed that a 3i to 3i activate takes no store backup today (backup_store's one call is inside the schema-differs branch). Q1 yes: row 185 (Gitea #1683, P19 403) lands before the window and Part A's NEW moves to its landing. Q2: keep matrix items (d) and (f); tester-probe isn't Jason's identity and no request carries jason.woltje or a credential. Q3: keep the one W9 assistant turn on the assistant instance as configured, no role or model change, with the 10-minute report rule.",
"Part B stays drafted and stopped. Mos carries Jason's steps J1-J6 (comment 345 on P19 385) in their morning summary. Mos checks the plan again at row 185's landing sha before W0.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the
// mos-0930 plan check on the mos-0928 plan draft 2. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-mos0930.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 = 386;
const SIG = "Posted 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 BODY = p(
"Decision (Mos, mos-0930, 2026-10-20T07:17Z), recorded by Sage: Part A draft 2 of the mos-0928 plan (agents/sage/work/window-plan/repin-rows-177-185.md at 2ea05700b, sha256 2194b9b04321) passes Mos's check before W0. NEW is 490e2771d (row 185), tool blob f8051d81cc18, 52 commits past 89af38a83, no schema step. Mos read the row 185 diff: a same-schema W5 activate backs up the stopped store at SCHEMA 3i, a W5 failure doesn't restore the store, and a same-schema rollback --restore of that backup passes the restore-mismatch check. Mos also ran the new P5 check on REPO, which passes.",
"Darkwing may start P1. W5 still waits for Mos's go after W3 and W4, and W9 waits for Mos's W5 verification. Filbert's two row 185 doc notes are P19 404, Sage's, for after the window.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,45 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row 175
// landing (98d29769e) and the row 176 brief amendment. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Row 175 landed as 98d29769e on refactor (Gitea #1673 closed, comment 29285). The tester reads and the W1-W7 tester writes are recorded as tester, and scripts/mosaic dispatches plugins, name and names to the CLI. The row 164 pins are untouched.",
"Sage's gate on 716ddcf1e plus the patch: 15 package suites, 2592 pass; 10 scripts/test-*.sh, 422 pass; 0 fail. Filbert approved round 3 (29279), and the patch-id matches the candidate 989699f7c.",
"Decision: the row 176 brief gains a stack-release change. activate --tester-port N writes the flag into the pointer's Console flags. A rollback, or an activate to a copy whose Console lacks the flag, drops it and says so. The activate refuses tester-port-target when the target copy can't take it. Without this, a hand-added --tester-port makes the next run refuse unit-flags-drift, and a rollback to 5fa9cc8f2 would fail to start the old Console. Row 175's doc follow-up F1 rides with 176.",
"The merged B+C window plan is drafted (agents/sage/work/window-plan/repin-merged-bc.md) and goes to Mos for a plan check once 176 lands. Live state is unchanged; the live copy is still 5fa9cc8f2.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T00:06:15.943Z",
"task": 386,
"comment": {
"status": 201,
"id": 303,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,45 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row 175
// row 176 landing (89af38a83) and the mos-0902 race decision. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Row 176 landed as 89af38a83 on refactor and is pushed (0a478dfc3..89af38a83). Gitea #1674 is closed with landing comment 29331. The Console now takes serve --tester-port N: a second listener on 127.0.0.1 that binds the tester and takes only the instances, routes and name writes. Every other listener, 7332 included, refuses x-mosaic-tester. stack-release activate --tester-port N writes the flag into the pointer and the unit.",
"Rocko built it on 473258a51 and Dewey approved round 2 (29322). The landed patch-id e6f0a0fb622b matches the candidate. Sage's gate run 2: 15 package suites, 2455 pass, 1 skip (the esbuild 0.28.2 editor rebuild), 0 fail; 10 scripts/test-*.sh, 422 selftests plus 156 queue node tests, 0 fail. The only public/ change is bus.js b0ffec0ce to 01c12b9e1bf2, inside the decision 123 go (mos-0900).",
"Decision (mos-0902): run 1 failed T34 with capture-failed. activate and pm-on name the activation by the second, so two in one second collide. The base from row 169 has the same race, so it did not block 176. Mos filed the fix as P19 391, after the window, in one row with Dewey's N1 (--plan console_for_target exits early) and N2 (tester_port_ok strips leading zeros). In the window, a capture-failed after activate means wait a second and retry.",
"Earlier decisions in this stretch: mos-0899 approved Darkwing's whole-store snapshot.mjs 639fd28a for S1, S2 and D3, and the restore-guard fix is P19 390. Live state is unchanged; the live copy is still 5fa9cc8f2. Next: Mos's formal check on the merged B+C window plan with NEW = 89af38a83.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T01:21:41.257Z",
"task": 386,
"comment": {
"status": 201,
"id": 316,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row 177
// design approval. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Decision (Sage, 2026-10-20T01:46Z): row 177 design approved. agents/dewey/work/queue-177/DESIGN.md, sha256 99ce578091bb4587, against brief blob f146cd772. The record is Gitea #1675 comment 29333. Dewey may write code now. Every test and mutant run waits for the slot, which Darkwing holds through W11 of the merged window.",
"Calls: (a) boot refuses a business whose human is tester. The live business human is jason, so nothing stops loading. (b) CLI tester thread verbs go to a follow-up row, which Sage files after 177 lands. (c) The UNIQUE(project,name) name-exists leak is accepted on the test system. Two notes go into the READMEs. N1: an assistant run in a tester thread can still message chief-of-staff, which the tester already reaches directly. N2: 7333 chat lets a loopback caller start assistant turns, and bus.tester (Jason's switch) fences it with tester-off.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row 177
// landing. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Landing (Sage, 2026-10-20T03:24Z): row 177, tester threads (Gitea #1675), landed on refactor as 1f994257a and round 2 9d9d95485 and was pushed c0d6bc7d5..7f519242e. Filbert approved round 2 in comment 29351. The combined patch-id 15f9c7bba807 equals the approved candidate.",
"Sage gate from a git worktree: GATE_PASSED, 25 steps all rc=0, node tests 2627 pass 0 fail, script suites 422 pass 0 fail. Queue row 177 done at rev 2112. #1675 closed with comment 29357. Not live yet, since the stack runs copy 89af38a83afa. Follow-up rows: N1 #threadFor owner check, N2 chat.mjs strings, tester talk --thread.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row 177
// round 2 decision. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Decision (Sage, 2026-10-20T02:49Z): row 177 goes to round 2. Filbert's round 1 verdict is changes, Gitea #1675 comment 29347. F1 blocks: a tester-thread assistant run raised a gated task.create, and the decision landed in the human's inbox with route_to human (probe output in 29347). DESIGN.md:82-84 assumed decisions needed crossRole, and that was wrong.",
"Round 2 (sage-1160 to Dewey): decision.raise from a run on a tester-owned thread refuses tester-thread-human when the route is human, with a test and a killed mutant. BUILD.md lists every path that stores to the human and records the design error as a deviation. The README sentence gets fixed. F2 (dispatch comment) and F3 (test text) ride along. N1 (#threadFor owner check) and N2 (chat.mjs strings) are follow-ups filed after 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(), task: TASK, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row 178
// briefing and rows 182-184. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Decision (Sage, 2026-10-20T03:33Z): row 178, the Console proxy proof (Gitea #1676, P19 385), is briefed. Owner Rocko, reviewer Dewey, gate owner Sage, queue revs 2113-2117. The brief (blob 1da7ac8753b6) says rows 180 and 181 also change stack-release.sh, and 180 changes webui-service.sh, so Rocko rebases onto their landings before in-review and Sage lands 178 after 180 (and after 181 if 181 is in review by then).",
"The row is code only. The slot token, the Traefik middleware and the unit flags stay activation steps for a window Mos plan-checks. Row 177 follow-ups are queued as rows 182 (N1 tester thread owner check, #1680), 183 (N2 chat.mjs strings, #1681) and 184 (tester CLI thread verbs, #1682), revs 2119-2124, briefs in docs/plans/2026-10-20_tester-thread-follow-ups.md. Owners get set at briefing.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,44 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row
// 178 landing and the row 182 gate start. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Landing (Sage, 2026-10-20T05:38Z): row 178, the Console proxy proof (Gitea #1676, P19 385), landed as b2b33aee5 with Rocko as author and is pushed (origin/refactor 7fc98cf19). Dewey approved round 2 in #1676 comment 29386. My gate on 02482094a plus the patch passed: node suites 2502 pass, 0 fail, scripts 422 pass, 0 fail, plus the queue suite 156. Patch-id 88e56a8a3afd matches the reviewed candidate. #1676 is closed (comment 29390).",
"Not live: the stack still runs copy 89af38a83afa. Activation (Traefik middleware, unit flags, restart, smoke) is a separate Mos-checked window. The proof is a partial fix, since same-uid seats can still read the file. Service uid separation stays Jason's call on P19 385.",
"Row 182 (Gitea #1680): Filbert approved round 1 in comment 29388, and my landing gate started at 05:38:55Z. Row 184 (Gitea #1682) rebases onto 7fc98cf19 next.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row 180
// landing. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Landing (Sage, 2026-10-20T03:55Z): row 180, activation ids within one second and plan-mode refusals (Gitea #1678, P19 391), landed on refactor as 1905e385c and was pushed c7421cabd..a757f8fe6. Dewey approved round 2 in comment 29361. The landing patch-id 31b1d502444e equals the approved candidate.",
"Sage gate from a git worktree: GATE_PASSED, 25 steps all rc=0, node tests 2482 pass 0 fail, script suites 422 pass 0 fail. Queue row 180 done at rev 2128. #1678 closed with comment 29364. Not live yet, since the stack runs copy 89af38a83afa. Row 181 (restore guard, P19 390) is briefed on this base, rev 2129, and row 178 (Rocko) rebased onto it.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row
// 181 landing, row 178 changes, row 183 approve and a time correction to comment 338. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Landing (Sage, 2026-10-20T04:54Z): row 181, the restore guard (Gitea #1679, P19 390), landed as ef48597d0 with Darkwing as author and is pushed (origin/refactor 931651f2d). Filbert approved round 1 in #1679 comment 29372. My gate on d7184aeac plus the patch passed: node suites 2489 pass, 0 fail, scripts 422 pass, 0 fail, plus the queue suite's 156. Patch-id 1fef78100554 matches the reviewed candidate. #1679 is closed (comment 29377). Follow-ups F1 (task_comment_cursors has no time column) and F2 (the refusal text at stack-release.sh:909) are in agents/darkwing/work/queue-181/BUILD.md. Not live: the stack still runs copy 89af38a83afa.",
"Row 178 (Gitea #1676, P19 385): Dewey asked for changes in #1676 comment 29374. Five header and file mutants survived, and an empty proof on a loopback Host needs a test. Rocko reopened it (rev 2146) and rebases round 2 onto 931651f2d. Row 183 (Gitea #1681): Dewey approved in comment 29376, and my landing gate is running. Correction to comment 338: its body says 04:30Z, but I posted it at 04:28:29Z.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row
// 182 landing. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Landing (Sage, 2026-10-20T05:49Z): row 182, a tester message reaches only a thread the tester owns (Gitea #1680), landed as 33ab5eeb9 with Darkwing as author and is pushed (origin/refactor 33ab5eeb9). Filbert approved round 1 in #1680 comment 29388. My gate on 7fc98cf19 plus the patch passed: node suites 2506 pass, 0 fail, scripts 422 pass, 0 fail, plus the queue suite 156. Patch-id fa9a8988ebfc matches the reviewed candidate. #1680 is closed (comment 29393). Not live: the stack still runs copy 89af38a83afa.",
"Row 184 (Gitea #1682) is rebased onto 7fc98cf19, and Dewey is rerunning its tests in the slot before the in-review move to Filbert.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the rows
// 178/181 review status and the row 183 briefing. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Status (Sage, 2026-10-20T04:30Z): row 181, the restore guard (Gitea #1679, P19 390), is in review round 1 with Filbert (comment 29370, revs 2135-2136, manifest 46a4433ca410). Darkwing's slot run had stack-release.test.mjs at 116/116 and all 14 mutants killed. Row 178, the Console proxy proof (Gitea #1676, P19 385), is in review round 1 with Dewey (comment 29368, revs 2131-2134, manifest e8f7a258b955).",
"Order: 181 lands first. Rocko then rebases 178 onto it and reruns every touched test file, and 178 gets a round 2 on the new base. Row 183 (Gitea #1681, chat.mjs transport strings) is briefed for Researcher with reviewer Dewey (revs 2137-2142, brief commit 5a8c1340c, pushed). Rows 182 and 184 stay queued until a builder is free. Nothing here touches the live stack.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T04:28:29.824Z",
"task": 386,
"comment": {
"status": 201,
"id": 338,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row
// 183 landing and the rows 182 and 184 briefing. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Landing (Sage, 2026-10-20T05:05Z): row 183, chat.mjs names the transport it is given (Gitea #1681), landed as d1272e714 with Researcher as author and is pushed (origin/refactor d1272e714). Dewey approved round 1 in #1681 comment 29376. My gate on 931651f2d plus the patch passed: node suites 2490 pass, 0 fail, scripts 422 pass, 0 fail, plus the queue suite 156. Patch-id af1cad0940dc matches the reviewed candidate. #1681 is closed (comment 29381). Not live: the stack still runs copy 89af38a83afa.",
"Rows 182 (Gitea #1680, Darkwing builds, Filbert reviews) and 184 (Gitea #1682, Dewey builds, Filbert reviews) are briefed at blob 3b4313cb9ffa (brief commit 841c56d05, revs 2154-2161). Row 178 round 2 is rebased onto 931651f2d, and Rocko is running its tests and mutants in the slot.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row
// 184 landing. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Landing (Sage, 2026-10-20T06:16Z): row 184, tester thread verbs in the CLI (Gitea #1682), landed as 87aca4cb8 with Dewey as author and is pushed (origin/refactor 9f5153deb). Filbert approved round 1 in #1682 comment 29396 with no findings. My gate on dd7656cbf plus the patch passed: node suites 2511 pass, 0 fail, scripts 422 pass, 0 fail, plus the queue suite 156. Patch-id ad846db177b3 matches the reviewed candidate. #1682 is closed (comment 29397). Not live: the stack still runs copy 89af38a83afa.",
"That ends the mos-0870 assignment: rows 177, 178, 180, 181 and follow-ups 182-184 are all landed and pushed. Row 178 activation (Console proxy proof flags, restart and smoke) still needs its own Mos-checked window.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the
// row 185 landing and the mos-0928 plan draft 2. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r185-landing.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 = 386;
const SIG = "Posted 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 BODY = p(
"Landing (Sage, 2026-10-20, Q173 row 185): 490e2771d lands row 185 (Gitea #1683, closed in 29405; P19 403, done). Every stack-release.sh activate now backs up the stopped store under its activation id before any migration, so Q173's backup-first rule holds for a 3i to 3i re-pin. Darkwing built it, Filbert approved round 1 (29404), and Sage's gate passed (node 2518/0, scripts 422/0, queue 156). Pushed in b5d662cab..2ea05700b.",
"mos-0928 plan draft 2 (agents/sage/work/window-plan/repin-rows-177-185.md at 2ea05700b) sets Part A's NEW to 490e2771d, 52 commits past OLD 89af38a83, still 3i with no migration. P5 now checks that REPO's four service scripts match NEW. The plan goes to Mos for the check before W0. Nothing live changed: the stack runs copy 89af38a83afa.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,45 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the
// W11 STATE report of the mos-0928 Part A re-pin window (rows 177 and 180-185). Sage's own key; the sync key reads
// back. Prints no secret.
// Usage: node comment-386-r185-window.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 = 386;
const SIG = "Posted 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 BODY = p(
"Landing under Q173 (mos-0928 Part A), recorded by Sage: the re-pin window for rows 177 and 180-185 passed. NEW 490e2771d is live for mosaic-stack: activation 20261020T075800Z-490e2771d3d0, pm true, store 3i, tester listener 127.0.0.1:7333. The W5 activate backed up the stopped store first, to backups/stack/20261020T075800Z-490e2771d3d0 (STAMP 07:58:00.381Z, SHA256SUMS 399f7105). Row 178's code is live with --proof-header and --proof-file unset, and the 7332 request matrix is byte-identical before and after (93c07e15). Nothing was rolled back. Darkwing ran it as operator and tester, Mos gave each go, and Sage checked each step.",
"STATE report: agents/darkwing/work/window-rows-177-185/STATE.md, sha256 b586f11c9b63, pushed in 21aa1cab0 on refactor. Transcript W.txt final sha256 edd4622c5c45. Two stops, both reported to Mos before going on. First, the W9 assistant run was still up past the plan's 10-minute rule, because the launcher's 900 s thread idle stop ends it by design. It ended itself at 08:16:18Z (a plan gap, mos-0948). Second, the W5 backup holds the old chief's two end rows, written by the W5 stop just before the backup, so Mos's mos-0949 rule that the backup equals S1 failed (accepted, mos-0950). The per-row check shows S1 is a prefix of the backup and the backup a prefix of S2, so no old row changed.",
"Two items STATE.md leaves out. Filbert's two row 185 doc notes are P19 404, Sage's, for after the window. Mos's corrected stamps from the STATE commit c43b730 (correct mos-0933..0937 stamps): mos-0933 before 07:23:28Z, mos-0934 before 07:37:04Z, mos-0935 and mos-0936 before 07:37:58Z, mos-0937 before 07:42:17Z, and Mos's live 7333 limits GET before 07:37:04Z. STATE.md lists only mos-0934, mos-0936 and mos-0937. Part B, the row 178 proxy proof activation, has not run and waits for Jason on P19 385.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,44 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row 175
// row 176 landing (89af38a83) and the mos-0902 race decision. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 386;
const SIG = "Posted 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 BODY = p(
"Decision (mos-0907): Mos closed the formal check of the merged B+C window plan and gave the W0 go. The plan is draft 9 (sha256 523f20e03be8aa11), committed as c49de08e8 and on origin. Draft 9 applies Mos's four text fixes to draft 8 (mos-0906): two stale line refs, the snapshot paragraph rewritten to match snapshot.mjs 639fd28a, and a D3 rule for a stopped bus (--stopped in place of --held; a store-unclean refusal stops the window and goes to Mos).",
"Darkwing's TP=1 P3 rehearsal on 89af38a83 passed on a scratch root: SHA256SUMS c26e1a4f, 36 of 36 files match, and the only nonzero exits are the five expected refusals. D3 counted 1 event after the backup, the restore without --events refused post-backup-events, and the rerun with --events 1 passed. The tester port was dropped on rollback.",
"The window now runs on the test system under Q173. Darkwing operates it and Mos verifies. W5 (activate with --migrate) and W7 (pm-on) each wait for Mos's own go. NEW is 89af38a83. The STATE report goes here and on P19 382 at W11. Row 177 (tester threads) has started with Dewey writing the design, which Sage approves before any code.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,47 @@
// Sage, 2026-10-20: plan draft 9 W11 ("Sage files it on P19 386 and 382"). Comments on P19 386 with the merged B+C
// window STATE summary. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-window-bc.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 = 386;
const SIG = "Posted 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 BODY = p(
"Merged B+C window done. NEW 89af38a83 is live for mosaic-stack with its PM, the store is on 3i, and the tester listener is on 127.0.0.1:7333. W0 to W10 passed, no stop condition fired and nothing was rolled back. Darkwing operated it and Mos verified each step. Sage checked the STATE report (agents/darkwing/work/window-merged-bc/STATE.md, sha256 7be42b6bcab5657d) against the transcripts in window/ and found it consistent.",
"W5 activate 20261020T013708Z-89af38a83afa: exit 0, 3h to 3i, gate passed. Store backup ~/.mosaic-dev/backups/stack/20261020T013708Z-89af38a83afa, STAMP 2026-10-20T01:37:08.917Z, SHA256SUMS ef78f7d4, rechecked OK at S2b. W6: the tester set plugins.harness to [claude-code] (transaction 0a63a1dc). W7 pm-on 20261020T014103Z-89af38a83afa launched chief rc78c9700c8e9. W8 carried the seven skills, and each matches NEW. The bus and Console units now run from the NEW copy, the Console with --tester-port 7333, and 7332 healthz is 200.",
"W9: every tester read exited 0, and the chief answered a tester talk. A coder the chief started answered with its four skills, made no tool calls, and was stopped at 02:01:41Z. W10: the browser on 7333 showed only tester forms and sent only GET requests. 7332 answers x-mosaic-tester with 403, and 200 without the header.",
"Three deviations, each accepted by Mos. (1) W8 ran from the repo pinned to NEW, because a stack copy has no skill.sh or skills/ (mos-0914, mos-0915). Sage's carry doc was wrong, and its fix is due under #394. (2) The W9 coder was started by the chief, since TESTER_VERBS has no role.launch (mos-0917). (3) The tester Settings has no Token limits panel, by design. The limits were read as the tester (tokens []), and no human Console read ran (mos-0920, mos-0922).",
"S2b compared S2 with the W5 backup: every table matches on the rows they share, and nothing exists only in the backup. Rows added since W5 sit in deliveries, messages, role_claims, task_comment_cursors, task_snapshots and usage_records, and a restore would drop them. One point the report leaves out: the backup has 1384 events and 88 role_claims against S1's 1383 and 87, so the OLD bus wrote one of each between S1 (01:34:49Z) and the backup (01:37:08Z). That's expected with OLD still live.",
"Next: #394 fixes carry-past-row64.md and plan line 222. Dewey builds row 177 once the slot is clear. P19 390 (restore guard) and 391 (activation-id race) stay open. P19 382 closes with this report.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/Q173/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the Q173 task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 390 with the
// queue row filed for it. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-390-row181.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 = 390;
const SIG = "Posted 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 BODY = p(
"Decision (Sage, 2026-10-20T02:32Z): filed as queue row 181, Gitea #1679, after row 180 is done, since both change stack-release.sh. Owner Darkwing, reviewer Filbert, gate owner Sage. Brief: docs/plans/2026-10-20_stack-release-after-window.md, \"Brief: the restore guard counts every row a restore drops (row B)\", blob aafa471ae (commit c7f3beb17).",
"The guard counts rows after STAMP in events and in every table written without an event, starting with usage_records and instance_names. A table the store lacks counts as 0. It refuses post-backup-events unless --events N equals the total, and names each nonzero table. Flag and code names stay. Mos's mos-0927 R1 is in: a test classifies every schema table into exactly one of events, counted, always-with-an-event (writer cited), or not-counted-by-time (reason given). BUILD.md records any UPDATE that leaves the time column unmoved.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/restore guard/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the expected task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,45 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 386 with the row 175
// landing (98d29769e) and the row 176 brief amendment.
// Copy for P19 390 (mos-0894): Sage's scope for the --restore guard fix, wider than the task title. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-386-r175.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 = 390;
const SIG = "Posted 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 BODY = p(
"Scope note from Sage, the owner, as mos-0894 asked for a different scope. Darkwing's P3 work (sage-1123) found that usage_records and instance_names aren't the only tables the guard misses. A tester project.create writes projects, and a tester message.send writes messages and maybe message_threads. Neither adds an event, so each one passes the events-only count at stack-release.sh:290 and a restore drops it with no refusal.",
"So I'm scoping the fix to the whole store, not to two named tables. Before a restore, the guard compares the live store with the backup's bus.sqlite for every table in sqlite_master, by rows after STAMP where the table has an at column and by a count difference where it doesn't. Any difference outside events refuses with a named code that lists the tables and counts. Events keep the --events N rule. A table added by a later migration is covered without a code change, and a test proves that.",
"For tonight's window, plan draft 5 (sha256 c398e1530fa4a129, uncommitted) stops before any restore on the same whole-store check. Darkwing's new snapshot.mjs runs it, and Mos reads that script again before W1. The row is filed after the window with its own brief and Gitea issue, citing this task.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/--restore guard/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the restore guard task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T00:43:29.910Z",
"task": 390,
"comment": {
"status": 201,
"id": 312,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,43 @@
// Sage, 2026-10-20: mos-0882 ("every landing or decision gets a Vikunja comment"). Comments on P19 391 with the
// queue row filed for it. Sage's own key; the sync key reads back. Prints no secret.
// Usage: node comment-391-row180.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 = 391;
const SIG = "Posted 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 BODY = p(
"Decision (Sage, 2026-10-20T02:32Z): filed as queue row 180, Gitea #1678. Owner Darkwing, reviewer Dewey, gate owner Sage. Brief: docs/plans/2026-10-20_stack-release-after-window.md, \"Brief: activation ids and plan-mode refusals (row A)\", blob aafa471ae (commit c7f3beb17). The row also carries Dewey's row 176 N1 (--plan refusals in console_for_target through check_refuse, new code tester-port-invalid) and N2 (webui-service tester_port_ok length check before stripping zeros).",
"The id keeps today's shape as its prefix, so old ids in stack-rollback/, backups/stack/ and the pointer still parse and --restore takes both. The test pins the clock with a date stand-in. Row 180 is briefed. Code can start now, and test runs wait for the gate slot.",
);
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, comment: 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 t = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== 19 || !/activation id collides/.test(t.json?.title ?? "")) throw new Error(`task ${TASK}: ${t.status}, not the expected task`);
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: BODY }, 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(`read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,37 @@
// Sage, 2026-10-19: brief, issue and row ids as task comments on P19 379-380, with Sage's own key (bot user 13). The sync key
// reads each comment back. Comments only: no title, description, done, label or bucket change. Prints no secret.
// Usage: node comment-mos0845-rulings.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 SIG = "Added by Sage (T3 1ef1e4f8) with Sage's own key (bot user 13).";
const COMMENTS = {
379: [
"Brief docs/plans/2026-10-19_project-names-and-repos.md (blob 8164688ac66d, commit 97cc7f0a3), Gitea #1668, queue row 171 (queued). It recommends option B over mos-0845 D1's one registry: PRD 1.2 calls the business file's projects git repos, REQ-THREAD-2 has users create projects, and threads.project references the store's projects table. Sent to Mos for a ruling in sage-1017.",
],
380: [
"Brief docs/plans/2026-10-19_tester-no-holder-refusal.md (blob 70e493933030, commit 97cc7f0a3), Gitea #1667, queue row 170 (queued), owner Dewey, reviewer Filbert. Sent to Mos for approval in sage-1017.",
],
};
const key = readFileSync(KEY, "utf8").trim(), sync = readFileSync(SYNC, "utf8").trim();
const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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 };
}
writeFileSync(OUT, "", { flag: "wx" });
const out = { at: new Date().toISOString(), comments: [] };
for (const [task, lines] of Object.entries(COMMENTS)) {
const text = [...lines, SIG].map((l) => `<p>${esc(l)}</p>`).join("");
const r = await api("PUT", `/api/v1/tasks/${task}/comments`, { comment: text }, key);
const rec = { task: Number(task), status: r.status, id: r.json?.id, code: r.json?.code, message: r.json?.message };
const back = await api("GET", `/api/v1/tasks/${task}/comments`, undefined, sync);
rec.readBack = back.status;
rec.found = Array.isArray(back.json) && back.json.some((c) => c.id === r.json?.id);
out.comments.push(rec);
if (!rec.found) break;
}
writeFileSync(OUT, JSON.stringify(out, null, 1) + "\n");
console.log(JSON.stringify(out));
@@ -0,0 +1,61 @@
// Sage, 2026-10-18: Jason's rulings relayed by Mos in mos-0787. Comments on P19 332 or 333 (argv TASK),
// no state change: 333 stays open until row 161 (I6) lands, 332 until row 165 lands. Sage's own key (bot
// user 13). The sync key checks project and exact title first and reads the comment back. Prints no secret.
// Usage: node comment-mos0787.mjs https://tasks.woltje.com SAGE_TOKEN_FILE SYNC_TOKEN_FILE OUT_JSON 332|333
import { readFileSync, writeFileSync, existsSync } from "node:fs";
const [ORIGIN, KEY, SYNC, OUT, WHICH] = 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 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 TASKS = {
333: {
title: "install: a host process under another uid that reaches the Console port acts as Jason; accept it or fix it (Jason, row 87)",
text: [
"Jason ruled, relayed by Mos in mos-0787 (2026-10-18), on accepting this as a documented limit: \"yes\".",
"Row 161 (I6, Gitea #1656) writes it into the install docs' limits section as a known limit: a host process under another uid that reaches the Console's loopback port acts as Jason, and the install doesn't close that. The I6 brief and the design's limits line say so as of commit ab9f23d9e.",
"This task stays open until row 161 lands. Sage closes it then.",
],
},
332: {
title: "policy: no verb adds a role instance, so the chief of staff can't staff a role the business file doesn't declare (Jason)",
text: [
"Jason answered, relayed by Mos in mos-0787 (2026-10-18): \"I want a command and a WebUI surfacing for the modifications\".",
"Mos reads it as: Jason adds and changes role instances through a mosaic CLI command and a Console view instead of hand-editing the business file, the same pattern as Q169. Jason hasn't confirmed that reading yet.",
"Sage drafted the row only: queue row 165, Gitea #1662, brief docs/plans/2026-10-18_role-instance-verb.md (ab9f23d9e). It covers the CLI verb, the Console view, validation through the same path as mosaic business validate, and an audit entry in the organization change log. Human only; the chief of staff stays out until Jason says.",
"Nothing is built until Mos relays Jason's confirmation; the row stays queued. Until it lands, acceptance 314 keeps its step where Jason edits the business file.",
],
},
};
if (!Object.hasOwn(TASKS, WHICH ?? "")) throw new Error("TASK must be 332 or 333");
const TASK = Number(WHICH), TITLE = TASKS[WHICH].title, TEXT = TASKS[WHICH].text;
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 || task.json?.title !== TITLE) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}, title differs or missing`);
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,39 @@
// Append the fourth board-held.test.mjs:163 timeout (row 170 gate run 3, 2026-10-19) to P19 366 as a
// task comment, the way mos-0857 item 4 asks for it filed with its log. Refuses if 366 is done.
// Tokens are paths, never printed.
import { readFileSync, writeFileSync } from "node:fs";
const [ORIGIN, KEY, SYNC, OUT] = process.argv.slice(2);
if (!ORIGIN || !KEY || !SYNC || !OUT) throw new Error("usage: ORIGIN KEY SYNC OUT");
const TASK = 366;
const text = [
"Fourth occurrence, 2026-10-19 about 19:03Z, in Sage's full gate run 3 for row 170 (tree cb43dcb45, Dewey's 0b6ff260f rebased onto 228aeb503; range-diff 1:1 identical). Same test line and same step as comment 279: the first out() at :220, the Board read redirect after a Refresh click.",
"Every suite before packages/webui passed in that run. The broker change in row 170 applies only to tester sessions; this test opens none. Run 4 of the gate is under way on the same tree. Two of the three completed full gates on row 170 stopped here.",
"Log, packages/webui suite (pass 236, fail 1, 57.0 s), /mnt/storage/scratch/sage-r170/out-run3/pkg-webui.log:",
"test at tests/board-held.test.mjs:163:1",
"✖ a redirect answer on a Board read, a bus read, a conversation read, a board POST, a module read and a send signs the page out: one line, nothing stale, no further reads (12264.465595ms)",
"Error: Uncaught (in promise) Error: Condition timed out: !document.querySelector(\"#signed-out\").hidden: undefined",
"at Object.evaluate (packages/webui/tests/browser.mjs:48:35)",
"at async out (packages/webui/tests/board-held.test.mjs:168:5)",
"at async TestContext.<anonymous> (packages/webui/tests/board-held.test.mjs:220:5)",
"Lead for the fix: the wait at :57 is 100 page ticks of 50 ms, about 5 s of page time with no wall-clock budget, and the suite runs its browser files in parallel. Whether the Refresh click is dropped while a read is in flight, or the redirect read just lands after the budget, is the open question.",
].map((l) => `<p>${l.replace(/&/g, "&amp;").replace(/</g, "&lt;")}</p>`).join("");
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 task = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (task.status !== 200 || task.json?.project_id !== 19) throw new Error(`task ${TASK} read: ${task.status}`);
if (task.json.done) throw new Error(`task ${TASK} is done; refusing`);
writeFileSync(OUT, "", { flag: "wx" });
const rec = { at: new Date().toISOString(), task: TASK, title: task.json.title };
const r = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: text }, key);
Object.assign(rec, { status: r.status, id: r.json?.id, code: r.json?.code, message: r.json?.message });
const back = await api("GET", `/api/v1/tasks/${TASK}/comments`, undefined, sync);
rec.readBack = back.status;
rec.found = Array.isArray(back.json) && back.json.some((c) => c.id === r.json?.id);
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);
console.log(JSON.stringify(rec));
@@ -0,0 +1,39 @@
// Append the third board-held.test.mjs:163 timeout (row 170 gate run 1, 2026-10-19) to P19 366 as a
// task comment, the way mos-0857 item 4 asks for it filed with its log. Refuses if 366 is done.
// Tokens are paths, never printed.
import { readFileSync, writeFileSync } from "node:fs";
const [ORIGIN, KEY, SYNC, OUT] = process.argv.slice(2);
if (!ORIGIN || !KEY || !SYNC || !OUT) throw new Error("usage: ORIGIN KEY SYNC OUT");
const TASK = 366;
const text = [
"Third occurrence, 2026-10-19 about 18:58Z, in Sage's full gate for row 170 (tree 0c8a5321b, a cherry-pick of Dewey's 0b6ff260f on 2cef8cd35). Mos asked for it on P19 in mos-0857; this task already covers the same test line, so it goes here rather than as a new task.",
"Row 170 changes packages/bus/src/broker.mjs and the cli tester path. It doesn't touch packages/webui. The test loads the in-process broker, so Sage ran it alone: tests/board-held.test.mjs passed 2/2 three times on the candidate and three times on base 2cef8cd35. The rerun of the whole gate is under way on the rebased candidate.",
"Log, packages/webui suite (pass 236, fail 1), /mnt/storage/scratch/sage-r170/out-run1/pkg-webui.log:",
"test at tests/board-held.test.mjs:163:1",
"✖ a redirect answer on a Board read, a bus read, a conversation read, a board POST, a module read and a send signs the page out: one line, nothing stale, no further reads (11886.245007ms)",
"Error: Uncaught (in promise) Error: Condition timed out: !document.querySelector(\"#signed-out\").hidden: undefined",
"at Object.evaluate (packages/webui/tests/browser.mjs:48:35)",
"at async out (packages/webui/tests/board-held.test.mjs:168:5)",
"at async TestContext.<anonymous> (packages/webui/tests/board-held.test.mjs:220:5)",
"Mos's point (mos-0857): passing alone doesn't make it harmless, since it stopped a full gate. The fix is in the test's wait at :168, or in the page's sign-out path, not in rerunning gates.",
].map((l) => `<p>${l.replace(/&/g, "&amp;").replace(/</g, "&lt;")}</p>`).join("");
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 task = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (task.status !== 200 || task.json?.project_id !== 19) throw new Error(`task ${TASK} read: ${task.status}`);
if (task.json.done) throw new Error(`task ${TASK} is done; refusing`);
writeFileSync(OUT, "", { flag: "wx" });
const rec = { at: new Date().toISOString(), task: TASK, title: task.json.title };
const r = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: text }, key);
Object.assign(rec, { status: r.status, id: r.json?.id, code: r.json?.code, message: r.json?.message });
const back = await api("GET", `/api/v1/tasks/${TASK}/comments`, undefined, sync);
rec.readBack = back.status;
rec.found = Array.isArray(back.json) && back.json.some((c) => c.id === r.json?.id);
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);
console.log(JSON.stringify(rec));
@@ -0,0 +1,25 @@
// Append Filbert's row 167 notes 1 and 2 (comment 29062) to P19 370 as a task comment. Tokens are paths, never printed.
import { readFileSync, writeFileSync } from "node:fs";
const [ORIGIN, KEY, SYNC, OUT] = process.argv.slice(2);
if (!ORIGIN || !KEY || !SYNC || !OUT) throw new Error("usage: ORIGIN KEY SYNC OUT");
const TASK = 370;
const text = [
"Filbert's row 167 review (comment 29062, 2026-10-19) adds two items with the same cause. Sage folded them in here.",
"1. Right after a Reload click, settingsSnapshot is null while the old form is still enabled. An assistant submit in that gap shows a raw TypeError as its receipt (bus.js:646, caught at :669). Nothing posts and the draft is kept. The launch form already had this at base. The guard for this task should cover a null snapshot as well as a missing instance.",
"2. The reload wait at packages/webui/tests/settings-form.test.mjs:121 (receipt === '') is met by the click, not by the reread. It can flake but can't false-pass. The fix is to wait on a value only the reread produces.",
].map((l) => `<p>${l.replace(/&/g, "&amp;").replace(/</g, "&lt;")}</p>`).join("");
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 };
}
writeFileSync(OUT, "", { flag: "wx" });
const rec = { at: new Date().toISOString(), task: TASK };
const r = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: text }, key);
Object.assign(rec, { status: r.status, id: r.json?.id, code: r.json?.code, message: r.json?.message });
const back = await api("GET", `/api/v1/tasks/${TASK}/comments`, undefined, sync);
rec.readBack = back.status;
rec.found = Array.isArray(back.json) && back.json.some((c) => c.id === r.json?.id);
writeFileSync(OUT, JSON.stringify(rec, null, 1) + "\n");
console.log(JSON.stringify(rec));
@@ -0,0 +1,27 @@
{
"at": "2026-10-18T15:14:36.286Z",
"project": 19,
"created": [
{
"title": "D1: health-gated stack activate and rollback (row 162, #1659)",
"id": 336,
"index": 176,
"inverse": "mark done: POST /api/v1/tasks/336 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
},
{
"title": "D2: stack rehearsal on a scratch root (row 163, #1660)",
"id": 337,
"index": 177,
"inverse": "mark done: POST /api/v1/tasks/337 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
}
],
"problems": []
}
@@ -0,0 +1,66 @@
// Sage, 2026-10-19: decision 110. Files Mos's mos-0837 task, the 3i --migrate window with Jason,
// in project 19 with Sage's own key (bot user 13). Refuses if the title already exists. The sync
// key reads it back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-3i-window.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 TASKS = [
{ title: "3i --migrate window with Jason (bus schema 3h to 3i after row 126, #1620)",
description: p("Filed for Mos (mos-0837, 2026-10-19). Mos adds it to NOW. The window runs after row 126 lands on refactor and after the repin-8e7ea89a1 window, which stays at 3h (mos-0835).",
"What it runs: scripts/stack-release.sh activate ID BUSINESS --migrate on a target that carries row 126's 3h to 3i step (packages/bus/src/migrations/3h-3i.sql), with Jason present (stack-release.sh:10). The run takes the store backup before migrate-schema.mjs (stack-release.sh:667-668). Any re-pin past row 126 carries this step, the 129+164 window included if its target moves.",
"What it closes: scripts/land-work.sh runs scripts/land-work/read.mjs from its own checkout, and read.mjs refuses bus-schema when the store's version or digest differs from that checkout's store.mjs (read.mjs:64-66). The live copy 52f35e7a0 is at 3f and refactor is at 3h today, so a canonical land-work.sh run against the live bus already refuses. After the 8e7ea89a1 window the live bus is 3h; once row 126 lands refactor is 3i, and the refusal stays until this window. Broker landings run from the pinned copy and are not affected.",
"Restore caveat for Jason (mos-0837): the way back from 3i is restoring the pre-migrate backup (rollback BUSINESS --restore ACTIVATION, agents/dewey/work/queue-126/MIGRATION.md). A restore drops every usage record the 3i bus wrote after the backup, and --events N doesn't count usage records. Mos accepted this; it goes in the window brief.",
"Needs a window brief and Jason present. Nothing runs before then.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,66 @@
// Sage, 2026-10-18: decision 110. Files the board-held:163 sign-out timeout follow-up as a task in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-board-held-163.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 TASKS = [
{ title: "webui test: board-held.test.mjs:163 sign-out wait times out under suite load (twice on 2026-10-18)",
description: p("Mos (mos-0822): a second occurrence of this timeout gets filed as a task. This is the second.",
"1. Sage's row 155 r3 gate, 21:10Z, on the 155 tree (no row 154 patch): tests/board-held.test.mjs:163 failed at the :220 board POST case with Condition timed out: !document.querySelector(\"#signed-out\").hidden, after 13.9 s, load about 7.8. Singly it passed 3/3 at about 5 s. The rerun on the same tree passed.",
"2. Researcher's row 154 rebase run on d88613bac, about 21:30Z: the same test, the same wait at :220, after 11.9 s in the full webui suite. It passed singly 2/2. The test file is byte-equal to d88613bac's.",
"Cause as read so far: the test's wait helper (board-held.test.mjs:76) polls 100 times at 50 ms, a budget of about 5 s plus evaluate time, inside a test whose own timeout is 120000 ms. Under a parallel suite the sign-out after the redirect answer takes longer than that budget. docs-browser.test.mjs:57 already takes a tries argument for the same reason.",
"Follow-up: give the :220 wait, or the helper, a larger budget the way docs-browser.test.mjs does, and show the change doesn't hide a real stall, for example a mutant where the page never signs out still fails. Test-only change in packages/webui/tests, no public/ code, so no decision 123 review. Needs a queue row and an owner.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,68 @@
// Sage, 2026-10-18: decision 110. Files rows 162 (D1) and 163 (D2) as tasks in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-r149-r128-followups.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 TASKS = [
{ title: "D1: health-gated stack activate and rollback (row 162, #1659)",
description: p("Queue row 162, Gitea #1659. Decision 96 D track. Brief: docs/plans/2026-10-18_stack-release.md, section Stack activate and rollback, health-gated (D1), committed 0df05f2c5.",
"stack-release.sh adopt, activate, rollback and status: a pointer and an append-only log under the data root's state directory, a rollback-set capture, a stopped-store backup before a migration, a health gate after the start, and an in-run rollback when the gate fails.",
"No live run in the row. The first adopt and activate are a Mos window with Mos's go. Reviewer Filbert. Row 157 (I2) now waits for D1 and D2.") },
{ title: "D2: stack rehearsal on a scratch root (row 163, #1660)",
description: p("Queue row 163, Gitea #1660. Decision 96 D track. Brief: docs/plans/2026-10-18_stack-release.md, section Stack rehearsal on a scratch root (D2). Lands after row 162.",
"stack-release.sh rehearse: copies a backup into a scratch data root, runs the migration checks, starts the new copy's bus host with launches disabled and its Console on a loopback port without send, and runs D1's gate checks. No model calls.",
"A standing staging stack with model turns needs a second system config and model spend, so it's Jason's call and isn't briefed.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,65 @@
// Sage, 2026-10-19: decision 110. Files the ledger foreign-reference follow-up from the row 7
// 10-11 week run in project 19 with Sage's own key (bot user 13). Refuses if the title already exists. The sync
// key reads it back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-ledger-foreign-refs.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 TASKS = [
{ title: "Ledger: a #N for another tracker in a commit subject is counted as a stack issue (row 7, #1508)",
description: p("Found in the row 7 weekly run for 2026-10-11 to 10-17 (posted on #1508 as comment 29070, 2026-10-19).",
"packages/ledger/src/ledger.mjs:29 issueNumbers takes every #N in a commit subject. Subjects also cite Vikunja tasks and other trackers (\"Vikunja #259\", \"#237 proxy\", \"#225\"), and the ledger then reads those numbers as mosaicstack/stack issues. They land on old v1 issues, so 34 of the week's 131 table rows were #19 to #287, and the totals include them.",
"Fix to decide in the row: skip a #N that a tracker name (Vikunja, P19) precedes, or count only numbers at or above the first stack issue, with a test for each form seen in the log. Recompute the 10-11 week after it lands and post the corrected totals on #1508.",
"Needs a queue row and an owner. Ledger only, nothing live.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,69 @@
// Sage, 2026-10-18: decision 110. Files the repo-license Needs Jason task and the row 129 cursor follow-up as tasks in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-license-cursor.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 TASKS = [
{ title: "Needs Jason: repo license for mosaicstack/stack (row 154, decision 123)",
due: "2026-10-19T23:00:00Z",
description: p("Mos asked for this in mos-0716 under the decision 123 go for row 154 phase 2.",
"The repo root has no LICENSE file, and package.json says UNLICENSED and private. Row 154 imports open-source editor packages under MIT and BSD-2 licenses. Each package will ship its own license file next to the imported source, plus a NOTICE file.",
"Question for Jason: should mosaicstack/stack get a license, and which one?",
"Default if Jason doesn't answer by 2026-10-19: the repo stays UNLICENSED and private, and the imported notices stay in the import directory.") },
{ title: "Follow-up: latest-first cursor for WebUI chat thread messages (row 129, #1623)",
description: p("Row 129's design pages thread messages forward at 100 per page, starting from the oldest. A long thread then opens on its first page instead of the newest messages.",
"Follow-up: add a latest-first cursor to thread.messages and have the WebUI chat area open on the newest page. Not part of row 129. Sage approved the forward-only design in sage-0724.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,83 @@
// Sage, 2026-10-19: decision 110 and mos-0882 ("every landing or decision gets a Vikunja comment"). Files one P19 task
// for the Q173 test-system rows (175, 176, 177 and the merged B+C window, mos-0870), which had none, and comments on
// P19 382 with the row 169 landing (264756a30) and its doc follow-up row 179 (9e74925c2). 382 stays open: the fix is
// proven only when the merged window's activate runs past c216f47db. Sage's own key (bot user 13); the sync key reads
// back. Refuses if the title exists. Rollback is marking the new task done; deletion is Jason's or ops-01's. Prints no
// secret.
// Usage: node file-q173-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 TASK = {
title: "Q173 test system: tester writes (row 175), tester listener (176), tester threads (177), merged B+C window",
description: p(
"Filed for the mos-0870 assignment under DECISIONS Q173, which makes the dragon-lin mosaic-stack business a test system that agents test end to end as the tester, never as Jason. Overnight the queue runs under Q174 (mos-0882). Landings and decisions on these rows get a comment here.",
"Row 175 (#1673, Dewey): tester writes instances.set, routes.set and name.set, recorded as tester, plus the scripts/mosaic plugins dispatch. Round 2 folds in Filbert's r1 notes N1-N3. It is being rebased onto 9e74925c2 because row 179 changed docs/TOOLS.md.",
"Row 176 (#1674, Rocko): a Console listener for the tester on 127.0.0.1, port 7333 in the window. 7332 refuses x-mosaic-tester. It starts after 175 is done.",
"Row 177 (#1675, Dewey): tester threads, scoping. It comes after 176.",
"Merged B+C window after 169, 175 and 176 land: a store backup, activate --migrate to 3i, the row 169 two-phase harness set done as tester, the row 64 carry step, W6/W7 run by agents with the browser check on 127.0.0.1:7333, and a STATE report after. Darkwing operates and Mos verifies. Mos checks the plan doc first. Row 169 landed as 264756a30. Row 178 (P19 385) is not on the window path.",
),
};
const C382 = p(
"Row 169 landed as 264756a30 on refactor and is pushed (e7d47c346..ce5807989, Gitea #1666 closed, comment 29265). stack-release.sh activate now has --pm-after-harness, pm-on and the pending pointer, so a re-pin past c216f47db can start the PM after the harness kind is set rather than failing on harness-off.",
"Sage's gate on e7d47c346 plus the patch: 15 package suites 2419 pass, 10 scripts/test-*.sh 422 pass, 0 fail. Filbert approved round 1 (29263).",
"The doc follow-ups F1-F3 from that review landed as row 179, 9e74925c2 (pushed, #1677 closed): pm-on-down stops the Console, the working plugins set form until row 175 lands, and the business-unreadable refusal.",
"Live state is unchanged; the live copy is still 5fa9cc8f2. This task stays open until the merged B+C window's activate runs the two-phase start past c216f47db and passes (Q173, mos-0870).",
);
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: null, comment382: 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 before = await existing();
const clash = before.filter((t) => t.title === TASK.title);
if (clash.length) throw new Error(`exists: #${clash[0].id}; refusing`);
const t382 = await api("GET", `/api/v2/tasks/382`, undefined, sync);
if (t382.status !== 200 || t382.json?.project_id !== PROJECT || !/c216f47db/.test(t382.json?.title ?? "")) throw new Error(`task 382: ${t382.status}, title doesn't name c216f47db`);
writeFileSync(OUT, "", { flag: "wx" });
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, { title: TASK.title, description: TASK.description }, key);
rec.created = { title: TASK.title, status: r.status, id: r.json?.id ?? null };
if (r.status >= 300 || !Number.isSafeInteger(r.json?.id)) rec.problems.push(`create: ${r.status} ${r.json?.message ?? ""}`);
else {
rec.created.inverse = `mark done: POST /api/v1/tasks/${r.json.id} done true (sage key); 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, { readBack: back.status, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== TASK.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push("create read-back differs");
}
save();
const c = await api("PUT", `/api/v1/tasks/382/comments`, { comment: C382 }, key);
rec.comment382 = { status: c.status, id: c.json?.id ?? null };
if (c.status >= 300 || !Number.isSafeInteger(c.json?.id)) rec.problems.push(`comment 382: ${c.status} ${c.json?.message ?? ""}`);
else {
const back = await api("GET", `/api/v2/tasks/382/comments?per_page=50`, undefined, sync);
const found = list(back.json).find((x) => x.id === c.json.id);
Object.assign(rec.comment382, { readBack: back.status, found: Boolean(found) });
if (back.status !== 200 || !found) rec.problems.push(`comment 382 read-back: ${back.status}`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,92 @@
// Sage, 2026-10-18: decision 110. Files the review follow-ups from rows 137, 138, 140, 152 and 153 as tasks in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-r137-153-followups.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 TASKS = [
{ title: "bus: row 138 spec citation follow-ups: CHECK constraints, git read before key checks, unreachable #dispatch guard",
description: p("Row 138 (#1632) non-blocking notes from Dewey's round 1 review, agents/dewey/work/queue-138-review/r1/verdict.md.",
"N-c: decisions.spec_path and spec_commit have no CHECK constraint. The broker validates both before insert, so only a direct store write could put a bad value there. A CHECK needs a schema migration, so weigh it against the next schema bump.",
"N-e: a cited raise reads git before #dispatch validates its other keys, so a malformed raise with a valid citation costs a git read before refusing. Harmless; moving the key checks first would remove it.",
"The guard in #dispatch (broker.mjs:1164 at review time) can't be reached from request(), because routing uses 'spec_path' in args || 'spec_commit' in args. Keep it as a backstop, and say so in a comment, or test it directly.") },
{ title: "roles: name read_spec and write_record in the role .md contracts (row 138 N-d)",
description: p("Row 138 (#1632) note N-d from Dewey's round 1 review: the role .md contracts don't name the read_spec or write_record tools that row 138 added.",
"Row 149 (#1643) touches roles/pm.md and roles/chief-of-staff.md; whoever edits roles/*.md next can carry this. Grep packages/*/tests and scripts/test-*.sh for the files first; a test that reads one sends the change through the gate.") },
{ title: "docs: row 138 dated text: the 3e fixture 'pinned from 56639c99b' line and a stale chief-of-staff test title",
description: p("Row 138 (#1632) note N-f from Dewey's round 1 review, doc-only under decision 137.",
"The bus README and MIGRATION.md say the 3e fixture is 'pinned from 56639c99b's tree'. schema.sql is the same blob at 26a0b0fab, so it's accurate, only dated.",
"The business chief-of-staff.test title still lists four held actions; held now includes record.write. The assertion is right; only the title is stale. That one is a test file, so it goes through the gate.") },
{ title: "bus: the 120 s project socket timeout doesn't bound writeRecord's git chain",
description: p("Row 138 (#1632) round 2 notes from Dewey (2026-10-16T01:43Z). Round 2 fixed N-a by giving project verbs socket.setTimeout(120_000) in packages/bus/src/server.mjs.",
"1) GIT_TIMEOUT_MS bounds each git process, and writeRecord runs seven or more in sequence, so 120 s doesn't bound a whole write. The worst case is request-timeout, not a false success. 2) 120_000 is a literal, not derived from GIT_TIMEOUT_MS. 3) The long timeout is set before admission, unlike requestWeb's onAdmit. Harmless today, since refusals settle at once.",
"Fix: derive the timeout from the git limits and the number of git calls, and set it on admission, with a test.") },
{ title: "cli: row 152 test gaps: a waiting without launchable, and the tester-message line order",
description: p("Row 152 (#1646) non-blocking notes from Filbert's round 1 review (comment 28741).",
"1) No test sends a waiting without the launchable key, as an older bus would, so a !w.launchable mutant survives. The === false check and its comment carry it today.",
"2) A tester's message to a role outside the launch block prints the launch-block line, not 'a tester's message tells nobody to launch it', because the launchable check comes first. Both lines are true. No test pins the order; add one, or reorder if the tester line should win.") },
{ title: "project: row 153 follow-ups: keep the first error under a cleanup refusal, nested records repos, a shadowed name",
description: p("Row 153 (#1647) non-blocking notes 1, 3 and 4 from Filbert's round 1 review (comment 28727).",
"1) When init cleanup meets a replaced path, project-init-cleanup is thrown in place of the error that started cleanup (project-git in the test). Pass the first error as cause, or name it in the message, with a test.",
"3) projectRepo compares a repo with code roots only, so project B's repo may sit inside project A's repo. Outside row 153's brief; decide whether to refuse it.",
"4) In paths.mjs, for (const project of Object.values(business.projects)) shadows the project parameter. Harmless, since nothing after the loop reads it; rename it.") },
{ title: "bus: test a decision-backed staff.request spent across runs (row 140 note)",
description: p("Row 140 (#1634) round 3 note 1 from Filbert (comment 28724). No test spends a decision-backed staff.request across runs. The spend path is shared, and approval-binding covers it for message.send, so the gap is a test, not a known defect.",
"Filbert's probe across pm runs stopped because the message.send decision routed to an arbiter and a human resolve was refused agent-required. Add one case to packages/bus/tests/staff-request.test.mjs that resolves through the right route and shows the second run can't spend the same decision.") },
{ title: "seat: log when main doesn't follow root because main isn't an ancestor (row 137 finding 2)",
description: p("Row 137 (#1631) finding 2 from Dewey's live smoke plan, agents/dewey/work/queue-137/SMOKE.md (commit 22dbc2b28). Follow-up, not a blocker.",
"When main isn't an ancestor of the root's commit, seed() in packages/seat/src/working.mjs returns with no log line, event or refusal, and main keeps an older base. That is intended after a landing, since main is ahead of root. It also happens when the owner moves the root backward or checks out another branch there; before row 137 that reset main to the root. A coder's next push and a reviewer's next fetch then use the old main, and nothing says so.",
"No safety problem: nothing is forced, and a landing still checks its base. Add a host log line naming main and root, with a test.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,64 @@
// Sage, 2026-10-18: decision 110. Files the row 149 doc-only follow-up as a task in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-r149-docs.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 TASKS = [
{ title: "docs: business README merge-gate relaunch and launch.instances (row 149, #1643)",
description: p("Filbert's row 149 round 4 approval (comment 28929) left two doc-only follow-ups under decision 137. No test reads the file.",
"a. packages/business/README.md:194-196 says merge-gate lands once the technical arbiter resolves the decision. It doesn't mention the relaunch or the chief of staff's message to the relaunched instance (roles/chief-of-staff.md:49-53, roles/merge-gate.md:29-39).",
"b. A business has to list its merge-gate instance in launch.instances before the chief of staff can relaunch it (business.mjs:128-132, harness tools.mjs:115-118). The README should say so. An unlisted instance is refused instance-not-listed (cli launcher.mjs:198), so the failure is visible, not silent. Adding instances is Jason's business-file edit and is not part of this item.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,64 @@
// Sage, 2026-10-18: decision 110. Files the row 149 follow-up on adding role instances as a task in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-r149-instance-followup.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 TASKS = [
{ title: "policy: no verb adds a role instance, so the chief of staff can't staff a role the business file doesn't declare (Jason)",
description: p("Row 149 (#1643), from Dewey's 0572 probe. organization.set's instances only sets the model of an instance the business file already declares (packages/business/src/launch-settings.mjs:46-52), and business.mjs:130 refuses a launch.instances name the roles don't declare. Adding an instance is an edit to Jason's business file, a human step today.",
"Acceptance 314's Before the run section now says Jason adds the security-reviewer and merge-gate instances by editing the business file, then runs scripts/mosaic business validate (commit c0af7f248).",
"The question for Jason: should the chief of staff be able to add a role instance through a verb, the way Q169 lets it configure the assistant? Default until he rules: no verb; Jason edits the business file before the 314 run. Any verb is a policy change and gets its own row and brief.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,68 @@
// Sage, 2026-10-18: decision 110. Files the #1657 and #1658 follow-ups as tasks in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-r149-r128-followups.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 TASKS = [
{ title: "seat: a sandboxed launch hides a run directory under /tmp (#1657)",
description: p("Gitea #1657, found by Dewey in the row 149 round 3 singles (dewey-0579). Since 57948a81f (#1586) the sandbox binds the run directory at its own path, then <runDir>/tmp over /tmp (packages/seat/src/sandbox.mjs:119). A run directory under /tmp is then hidden inside the sandbox, and bwrap fails to chdir into it (sandbox.mjs:168).",
"Today it bites only tests run with TMPDIR unset or set to /tmp: packages/cli/tests/launcher.test.mjs fails 10 of 19 that way on base. The live data root isn't under /tmp.",
"Wanted: the launch refuses a run directory under /tmp with a named code before bwrap starts, or the sandbox mounts the private /tmp where it can't shadow the run directory. Plus a test that fails today. Not part of row 149; any row that edits sandbox.mjs can carry it.") },
{ title: "cli: README lists project-not-found, but the resolver's line reaches the user first (#1658)",
description: p("Gitea #1658, from Filbert's row 128 review (comment 28895 on #1622). Row 128 landed as ade628c8b.",
"packages/cli/README.md:104 lists project-not-found among the codes that print as mosaic: <code>. The CLI resolves every project reference through resolveProject first (packages/cli/src/thread.mjs:41-46), which prints its own no-project line with exit 2 (packages/cli/tests/thread-cli.test.mjs:127).",
"Fix: drop it from that list, or say the resolver's line comes first. Doc-only; any row that edits packages/cli/README.md can carry it.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,65 @@
// Sage, 2026-10-18: decision 110. Files the webui cold-load double-read follow-up as a task in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-r154-double-read.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 TASKS = [
{ title: "webui: a cold load of a docs URL reads the project file twice (found in row 154, #1648)",
description: p("Researcher's row 154 phase 2 read-count test found this on 2026-10-18. It is older than row 154.",
"Opening a #/docs URL cold reads the project file twice. The /api/config answer triggers a second render (packages/webui/src/public/bus.js, about line 1068), and docView reads the clean draft again.",
"Effects: one extra bus read on every cold open, and a commit that lands between the two reads can replace the page the user just saw. The row 154 test counts reads from what the open cost and asserts exact deltas after that, so it doesn't pin this behavior.",
"Follow-up: make the config re-render reuse the loaded draft, or skip the first render until config answers. The fix needs a test that a cold open reads exactly once. It touches public/, so it needs decision 123 review.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,65 @@
// Sage, 2026-10-18: decision 110. Files row 156's two review follow-ups (Filbert 29039) as a task in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-r156-followup.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 TASKS = [
{ title: "Landing runs: document the stuck-turn and restart cases, and prune #received (row 156 follow-up, #1651)",
description: p("Row 156 landed in 4f45cadc0 (#1651). Filbert approved round 2 in comment 29039 with two notes, neither blocking.",
"1. packages/cli/README.md, section Landing runs, doesn't yet say what happens when a landing turn hangs or the broker restarts. In both cases the run never reaches its first message.receive after the turn, so landRelease keeps answering landing: true and the release stays closed. The decision then falls back to row 149's path: the chief ends the run and launches the holder. BUILD.md in agents/dewey/work/queue-156/ has the wording. Doc-only, unless a test reads the README.",
"2. packages/bus/src/broker.mjs keeps #received, the runs that have asked message.receive, and never prunes it, the same as #ended. It grows by one entry per run for the broker's lifetime. A fix prunes both when a run ends, with a test and a mutant.",
"Needs a queue row and an owner. Nothing live.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,64 @@
// Sage, 2026-10-18: decision 110. Files row 164 (the channel kind) as a task in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-r164-channel.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 TASKS = [
{ title: "Channel kind: cli and webui as its first modules (row 164, #1661)",
description: p("Queue row 164, Gitea #1661, lead decision 149 (Mos mos-0726). Brief: docs/plans/2026-10-18_channel-kind.md, committed c742ecec2.",
"Q156 item 4 planned the cli and webui channel modules for rows 128 and 129. Row 128's refreshed brief dropped the step by mistake. This row adds the channel kind with both modules, directly after row 129, with no row between them.",
"Design packet first, which Sage sends to Mos because the row works next to the human proof. Reviewer Dewey. No live change in the row.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,66 @@
// Sage, 2026-10-18: decision 110. Files the row 165 credentials follow-up as a task in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-r165-credentials.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 TASKS = [
{ title: "Role instances that bind credentials: add a reviewer, coder, pm or cto instance through the command (row 165 follow-up, Q171)",
description: p("Mos, mos-0823, 2026-10-18: file this tonight. Jason's Q171 reading names a second reviewer as the example of an instance the human adds without hand-editing the business file. Row 165 (#1662, brief docs/plans/2026-10-18_role-instance-verb.md) keeps holder, credentials and tracker out of scope, so it can't do that.",
"Why: reviewer, coder, pm and cto each declare gitea and vikunja credentials in roles/*.json. security-reviewer, merge-gate, researcher and conductor-policy declare none, so acceptance 314 is covered by row 165.",
"Starting point: row 165's add verb refuses any definition that declares credentials, with a named refusal that says why, and it never writes an instance without its credential binding. This follow-up lifts that refusal only after Jason rules.",
"Open question, Mos to Jason in daylight on 2026-10-19: does the human bind credentials through this command? Until Jason rules, nothing binds credentials through it.",
"If yes: a brief for binding a credential slot to a new instance (which slot names, which file holds the secret path, never the secret itself, how a stale or missing slot refuses), Dewey on security review, Filbert second. Secrets never enter the repository or the business file.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,65 @@
// Sage, 2026-10-18: decision 110. Files row 156's two review follow-ups (Filbert 29039) as a task in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-r156-followup.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 TASKS = [
{ title: "Settings: an assistant save on a removed instance shows a raw TypeError (row 167 follow-up F1, #1664)",
description: p("Mos found this reading row 167's public.diff, 2026-10-19 (mos-0836, F1, non-blocking). Sage chose a follow-up over a row 167 delta.",
"In packages/webui/src/public/bus.js, assistantPayload reads settingsSnapshot.assistants[instance]. If a save answers with a snapshot that no longer has that instance while its form is open, entry is undefined and asShown throws a TypeError. The submit's catch shows the raw TypeError text as the receipt. Row 155's assistantSnapshot had the same exposure, so row 167 doesn't introduce it. Nothing is written: the payload throws before any fetch.",
"Fix: one guard in assistantPayload that answers 'This assistant is no longer in the settings. Read again.', plus a test. It is a public/ change, so it needs Mos's decision 123 go on its delta.",
"Needs a queue row and an owner, after row 167 lands. Nothing live.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,65 @@
// Sage, 2026-10-18: decision 110. Files row 156's two review follow-ups (Filbert 29039) as a task in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-r156-followup.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 TASKS = [
{ title: "Settings: the instance form and the launch settings forms go stale on each other's saves (row 167 follow-up, #1664)",
description: p("Darkwing found this while building row 167 (#1664), 2026-10-19. The row 167 brief doesn't cover it, so it stays out of 167 by Sage's ruling.",
"launch-settings.mjs and instance-settings.mjs (row 165) both write the business file through organizationWriter, so they share one revision. In packages/webui/src/public/bus.js the instance form keeps its own revision from the instances read. A launch, assistant or reset save leaves the instance form's revision stale, and an instance save leaves the settings snapshot stale. The next save in the other form answers revision-conflict. It fails safe: the draft is kept and no data is lost. It's the same failure as row 167 item (b).",
"Row 167's fix doesn't carry over directly, because an instance save answers with the instance view, not the launch settings. Two options: (1) re-read the other view after a save, a public/ change only; (2) have one answer carry both views, a server change. Option 1 is the smaller one. Either way it is a public/ change, so it needs Mos's decision 123 go.",
"Needs a queue row and an owner, after row 167 lands. Nothing live.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,73 @@
// Sage, 2026-10-19: decision 110. Files row 168's follow-ups (Dewey's two non-blocking notes from 29121 and
// the webui flake from the landing gate) in project 19 with Sage's own key (bot user 13). Refuses if a title
// already exists. The sync key reads each 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-r168-followups.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 TASKS = [
{ title: "Ledger README: say the tracker-name skip takes one space and any case (row 168 follow-up, #1665)",
description: p("Dewey's non-blocking note 1 on row 168 (Gitea #1665, comment 29121).",
"packages/ledger/README.md says the ledger skips a #N when \"the word right before it\" is Vikunja, P19, SetSpark or Launchpad. The code (issueNumbers in packages/ledger/src/ledger.mjs) matches with one space between, case-insensitive. The README should say both.",
"Doc-only. Nothing live reads the ledger.") },
{ title: "Ledger report: print which issue floor applied (row 168 follow-up, #1665)",
description: p("Dewey's non-blocking note 2 on row 168 (Gitea #1665, comment 29121).",
"The CLI drops issue numbers below --first-issue (default 1495) but the printed report and the --json output don't say which floor the run used, so a reader can't tell a --first-issue 1 run from a default one.",
"Fix: one line in the header and a field in --json, with a test for each. Ledger only.") },
{ title: "webui: editor browser test timed out once on #doc-save in the row 168 gate, not reproduced",
description: p("Row 168 landing gate, 2026-10-19, on 0ef8620f3 (code identical to f3a966ea4, which passed the full gate).",
"packages/webui/tests/editor-browser.test.mjs:93, \"the Console's CSP stands and the editor runs under it; hostile markdown on the load path runs and loads nothing\", failed with: Uncaught (in promise) Error: Condition timed out: !document.querySelector(\"#doc-save\").disabled (the wait at editor-browser.test.mjs:122). 230 of 231 passed in that run.",
"The file then passed 5 of 5 runs on its own, and the whole gate passed on rerun. A wait budget too tight for a loaded host is the likely cause, not yet shown. Row 154 owns the test.",
"Needs a look at the wait budget, not a fix in a hurry. Logs: /mnt/storage/scratch/sage-r168-out/gate2/pkg-webui.log.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,90 @@
// Sage, 2026-10-20: decision 110 and mos-0882 ("every landing or decision gets a Vikunja comment"), for mos-0928.
// Files one P19 task for row 185 (#1683, every activate backs up the stopped store), comments on P19 386 with the
// row and the Part A plan, and comments on P19 385 (row 178) with Part B's Jason steps. Sage's own key (bot user 13);
// the sync key reads back. Refuses if the title exists. Rollback is marking the new task done; deletion is Jason's or
// ops-01's. Prints no secret.
// Usage: node file-r185-partb.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 PSIG = "Posted 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 ps = (sig, xs) => xs.map((x) => `<p>${esc(x)}</p>`).join("") + `<p>${esc(sig)}</p>`;
const TASK = {
title: "Row 185: every stack-release.sh activate backs up the stopped store (#1683)",
description: ps(SIG, [
"Filed for mos-0928 Part A, the re-pin from 89af38a83 to the refactor head. Both copies are at schema 3i, and backup_store runs only when the schemas differ, so a same-schema activate takes no store backup today. The two live backups are 3f and 3h, and rollback --restore refuses both restore-mismatch. Q173 wants a backup before every change, and mos-0928 wants the row 181 restore guard in the rollback path.",
"The change: every activate backs up the stopped store under its activation id, before any migration. The in-run restore stays migration-only. --plan prints a backup line. Tests include a same-schema rollback --restore that refuses post-backup-events with one instance_names row after the stamp and passes with --events 1.",
"Owner Darkwing, reviewer Filbert, gate owner Sage. Brief docs/plans/2026-10-20_activate-always-backs-up.md, committed in 10e1bb495. Queue row 185, briefed. Part A's NEW becomes this row's landing commit.",
]),
};
const C386 = ps(PSIG, [
"Decision (Sage, 2026-10-20): mos-0928 asked for one plan doc. It is agents/sage/work/window-plan/repin-rows-177-185.md, committed in 10e1bb495 (local, not pushed yet). Part A re-pins 89af38a83 to the refactor head and brings rows 177 and 180-184 live under Q173, run by Darkwing, Mos and Sage like the merged B+C window. No schema step: both ends are 3i. Row 178's code goes live with the proof flags unset, and a 7332 request matrix run before and after shows the Console behaves the same.",
"Part A needs row 185 first (Gitea #1683, its own P19 task), because a same-schema activate takes no store backup today and the live backups can't be restored at 3i. NEW becomes row 185's landing. Mos checks the plan at that NEW before W0.",
"Part B, the row 178 proxy proof activation, is drafted in the same file and runs nothing. It waits for Jason (comment on P19 385).",
]);
const C385 = ps(PSIG, [
"For Jason's morning list (Sage, 2026-10-20, mos-0928): Part B of agents/sage/work/window-plan/repin-rows-177-185.md drafts turning on row 178's proxy proof for the 7332 Console. It changes who can reach the Console from outside, so under Q173 it waits for you. Nothing in it has run.",
"Your steps: J1 the go on Part B. J2 the uid separation on this task, so the Console runs under a uid the T3 seats don't share; today any seat can read the proof file. J3 the k3s side, a SealedSecret plus a Traefik customRequestHeaders middleware on the Console route in the mosaic-preview Argo app. J4 sealing the value from the slot file without printing it. J5 signing in through https://mosaic.woltje.com/ after the flags go on. J6 the go on the rollback order if that fails: flags off first, then the middleware.",
"Agent steps after your go: a tool row so activate can write the proof pair into the pointer (a hand webui-service.sh install would make every later activate and rollback refuse unit-flags-drift), the secret slot made with umask 077 and checked by stat only, Mos's plan check, the activate, and a request matrix with the proof on.",
"mos-0928 overrides three lines of the row 178 brief: that the turn-on doesn't wait for you, that the middleware falls under ops-01's R27 standing authority, and that uid separation doesn't hold it up. This task stays open.",
]);
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: null, comment386: null, comment385: 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 before = await existing();
const clash = before.filter((t) => t.title === TASK.title);
if (clash.length) throw new Error(`exists: #${clash[0].id}; refusing`);
const t386 = await api("GET", `/api/v2/tasks/386`, undefined, sync);
if (t386.status !== 200 || t386.json?.project_id !== PROJECT || !/Q173/.test(t386.json?.title ?? "")) throw new Error(`task 386: ${t386.status}, not the Q173 task`);
const t385 = await api("GET", `/api/v2/tasks/385`, undefined, sync);
if (t385.status !== 200 || t385.json?.project_id !== PROJECT || t385.json?.done) throw new Error(`task 385: ${t385.status}, not an open P19 task`);
rec.title385 = t385.json.title;
writeFileSync(OUT, "", { flag: "wx" });
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, { title: TASK.title, description: TASK.description }, key);
rec.created = { title: TASK.title, status: r.status, id: r.json?.id ?? null };
if (r.status >= 300 || !Number.isSafeInteger(r.json?.id)) rec.problems.push(`create: ${r.status} ${r.json?.message ?? ""}`);
else {
rec.created.inverse = `mark done: POST /api/v1/tasks/${r.json.id} done true (sage key); 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, { readBack: back.status, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== TASK.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push("create read-back differs");
}
save();
for (const [id, body, field] of [[386, C386, "comment386"], [385, C385, "comment385"]]) {
const c = await api("PUT", `/api/v1/tasks/${id}/comments`, { comment: body }, key);
rec[field] = { status: c.status, id: c.json?.id ?? null };
if (c.status >= 300 || !Number.isSafeInteger(c.json?.id)) { rec.problems.push(`comment ${id}: ${c.status} ${c.json?.message ?? ""}`); continue; }
const back = await api("GET", `/api/v2/tasks/${id}/comments?per_page=50`, undefined, sync);
const found = list(back.json).find((x) => x.id === c.json.id);
Object.assign(rec[field], { readBack: back.status, found: Boolean(found) });
if (back.status !== 200 || !found) rec.problems.push(`comment ${id} read-back: ${back.status}`);
save();
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,65 @@
// Sage, 2026-10-18: decision 110. Files the row 87 Console-port question for Jason as a task in project 19
// with Sage's own key (bot user 13). Refuses if a title already exists. The sync
// key reads each back. No labels, no assignees, no bucket moves. Rollback is marking the task done;
// deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-r87-console-port.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 TASKS = [
{ title: "install: a host process under another uid that reaches the Console port acts as Jason; accept it or fix it (Jason, row 87)",
description: p("Row 87 (#1581), design docs/plans/2026-10-18_containerized-install-design.md section 5. Mos (mos-0657, 2026-10-18) ruled that the routes open to a process under Jason's uid are covered by decision 105, and that the other-uid reach is not covered by any ruling: decision 95 accepted any same-UID process for the PoC only.",
"The reach: the Console is published on a loopback TCP port (7332 live). Reaching it is the proof (decision 105), the Authentik header only names the user, and a loopback Host skips the header check. Any host process that reaches the port can act as Jason, whatever its uid. Today's install has the same reach. dragon-lin has no other login user, and Mos found no rootful container on host networking and nothing on 7330.",
"Default: accept it as a documented limit in the install docs (row I6). Nothing waits on the answer until I6 writes its limits section.",
"Cheapest fix: the Console serves a 0600 unix socket instead of a TCP port, so only Jason's uid connects. Cost: the proxy in front of the Console must run on this host under Jason's uid or an allowed group and speak to a unix upstream, and the loopback browser path goes away or moves behind a forwarder for Jason's uid only. Mos or Jason confirms the proxy side; no development seat reads the proxy config. The fix lands in row I4.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,66 @@
// Sage, 2026-10-13: decision 110 and Mos sage-0147. Files the unreproduced test-release activation
// failure from the row 104 gate as one task in project 19 with Sage's own key (bot user 13). Refuses if
// the title already exists. The sync key reads it back. No labels, no assignees, no bucket moves.
// Rollback is marking the task done; deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-release-flake.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 TASKS = [
{ title: "test-release: healthy activation failed once in the row 104 gate, not reproduced",
description: p("Sage's row 104 gate (#1598), run 2026-10-13 22:25-22:32Z in a detached worktree of f00613106 plus Darkwing's build.patch, DOCKER_HOST unset, Docker 29.7.2. Every package suite and the scripts before it passed. scripts/test-release.sh then went 11 passed, 3 failed and the gate stopped.",
"Failed checks: (1) \"healthy activation succeeds\" (exit 1, expected 0); (2) \"pointer written with valid fields\" (ENOENT on the sandbox data/state/active.json, because activation wrote no pointer); (3) \"repeat activation succeeds (log grows)\" (exit 1, expected 0). The fault-injection and refusal checks before them passed.",
"Not reproduced: scripts/release.sh activate alone, with the same sandbox config shape in the same tree, exited 0 (\"activate OK -> 0.0.12\"); a full rerun of test-release.sh in the same tree went 14 passed, 0 failed (22:39Z); test-task.sh then passed 98/0. Row 104 doesn't touch scripts/release.sh or the image. Darkwing's gate of the same candidate on 817ddfea6 passed test-release.",
"The cause is unknown because expect_exit sends activation's stdout and stderr to /dev/null. First step: keep that output in a file under the sandbox and print it on a failure, so the next occurrence names its reason. Logs: /mnt/storage/scratch/sage-gate-104-out/script-release.log and script-release-rerun.log.",
"Mos (sage-0147): if it shows up again during the row 104 re-pin gate, stop and tell Mos.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,72 @@
// Sage, 2026-10-14: decision 110. Files the two unreproduced one-off failures from the row 110 round 2 gate (a webui
// browser test and a test-task model recall check) as two tasks in project 19 with Sage's own key (bot user 13). Refuses if
// the title already exists. The sync key reads it back. No labels, no assignees, no bucket moves.
// Rollback is marking the task done; deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-webui-flake.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 TASKS = [
{ title: "webui: redirect sign-out browser test timed out once in the row 110 gate, not reproduced",
description: p("Sage's row 110 round 2 gate (#1604), run 2026-10-14 01:29:38-01:32:15Z in a detached worktree of badfa52b9 plus Darkwing's round 2 build.patch (manifest bdaf7f47). Every package suite before webui passed. packages/webui then went 107 passed, 1 failed and the gate stopped.",
"Failed test: \"a redirect answer on a Board read, a bus read, a conversation read, a board POST, a module read and a send signs the page out: one line, nothing stale, no further reads\" (board-held.test.mjs:220, via out() at :168). Error: Condition timed out: !document.querySelector(\"#signed-out\").hidden, after 9313 ms. Load average was about 4.8 at the time.",
"Not reproduced: two webui suite reruns in the same worktree went 108 passed, 0 failed each (ending 01:34:47Z). A second whole-gate run in the same tree passed webui 108/0 and every other package suite. Row 110 doesn't touch packages/webui. Darkwing's round 1 and round 2 gates of the row passed it.",
"The cause is unknown. A first step is to log which of the six redirect cases was waiting when the condition timed out, so the next occurrence names it. Logs: /mnt/storage/scratch/sage-gate-110r2-out/try1/pkg-webui.log and webui-rerun1.log, webui-rerun2.log.",
"Same rule as #258 and #261 (Mos sage-0150): one recurrence in a gate is a note; two in the same gate mean stop and tell Mos.") },
{ title: "test-task: forked child did not recall the ancestor code word once in the row 110 gate, not reproduced",
description: p("Sage's row 110 round 2 gate (#1604), second run, 2026-10-14, in a detached worktree of badfa52b9 plus Darkwing's round 2 build.patch (manifest bdaf7f47). Every package suite and every script before it passed. scripts/test-task.sh then went 97 passed, 1 failed.",
"Failed check: \"forked child recalled ancestor code word\" (scripts/test-task.sh:486). The checks either side passed: \"fork base: teach succeeds\" and \"fork child recalls ancestor context\" (exit 0), then \"ancestor session untouched by fork\". So the child run succeeded, but its result.json response did not contain mosaico. The check calls a real model, so the reply can vary.",
"Not reproduced: test-task.sh alone in the same tree then went 98 passed, 0 failed. Row 110 doesn't touch run-task.sh, the pi adapter or sessions. Darkwing's round 1 and round 2 gates passed it.",
"The cause is unknown because the suite's sandbox (mktemp, removed by an EXIT trap) took the run record with it. First step: print the child's response on a failure, so the next occurrence shows what the model said. Logs: /mnt/storage/scratch/sage-gate-110r2-out/try2/script-task.log and script-task-rerun.log.",
"Same rule as #258 and #261 (Mos sage-0150): one recurrence in a gate is a note; two in the same gate mean stop and tell Mos.") },
];
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: [], 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 before = await existing();
const clash = before.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 body = { title: t.title, description: t.description, ...(t.due ? { due_date: t.due } : {}) };
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, body, 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 (pm); 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, project_back: back.json?.project_id, due_back: back.json?.due_date, done: back.json?.done });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.done) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);
@@ -0,0 +1,19 @@
{
"at": "2026-10-19T23:48:54.728Z",
"project": 19,
"created": {
"title": "Q173 test system: tester writes (row 175), tester listener (176), tester threads (177), merged B+C window",
"status": 201,
"id": 386,
"inverse": "mark done: POST /api/v1/tasks/386 done true (sage key); delete is Jason's or ops-01's",
"readBack": 200,
"done": false
},
"comment382": {
"status": 201,
"id": 291,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,20 @@
{
"at": "2026-10-19T19:49:11.621Z",
"task": 308,
"title": "bus: a role holding organization.change can read the organization revision (queue row 134)",
"problems": [],
"comment": {
"status": 201,
"id": 283,
"readBack": 200,
"found": true
},
"update": {
"status": 200,
"inverse": "mark undone: POST /api/v1/tasks/308 done false (sage key)",
"readBack": 200,
"done": true,
"titleSame": true,
"descriptionSame": true
}
}
@@ -0,0 +1,87 @@
{
"at": "2026-10-18T12:41:36.176Z",
"project": 19,
"created": [
{
"title": "bus: row 138 spec citation follow-ups: CHECK constraints, git read before key checks, unreachable #dispatch guard",
"id": 323,
"index": 163,
"inverse": "mark done: POST /api/v1/tasks/323 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
},
{
"title": "roles: name read_spec and write_record in the role .md contracts (row 138 N-d)",
"id": 324,
"index": 164,
"inverse": "mark done: POST /api/v1/tasks/324 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
},
{
"title": "docs: row 138 dated text: the 3e fixture 'pinned from 56639c99b' line and a stale chief-of-staff test title",
"id": 325,
"index": 165,
"inverse": "mark done: POST /api/v1/tasks/325 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
},
{
"title": "bus: the 120 s project socket timeout doesn't bound writeRecord's git chain",
"id": 326,
"index": 166,
"inverse": "mark done: POST /api/v1/tasks/326 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
},
{
"title": "cli: row 152 test gaps: a waiting without launchable, and the tester-message line order",
"id": 327,
"index": 167,
"inverse": "mark done: POST /api/v1/tasks/327 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
},
{
"title": "project: row 153 follow-ups: keep the first error under a cleanup refusal, nested records repos, a shadowed name",
"id": 328,
"index": 168,
"inverse": "mark done: POST /api/v1/tasks/328 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
},
{
"title": "bus: test a decision-backed staff.request spent across runs (row 140 note)",
"id": 329,
"index": 169,
"inverse": "mark done: POST /api/v1/tasks/329 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
},
{
"title": "seat: log when main doesn't follow root because main isn't an ancestor (row 137 finding 2)",
"id": 330,
"index": 170,
"inverse": "mark done: POST /api/v1/tasks/330 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
}
],
"problems": []
}
@@ -0,0 +1,17 @@
{
"at": "2026-10-18T16:46:52.513Z",
"project": 19,
"created": [
{
"title": "docs: business README merge-gate relaunch and launch.instances (row 149, #1643)",
"id": 344,
"index": 184,
"inverse": "mark done: POST /api/v1/tasks/344 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
}
],
"problems": []
}
@@ -0,0 +1,17 @@
{
"at": "2026-10-18T13:08:41.834Z",
"project": 19,
"created": [
{
"title": "policy: no verb adds a role instance, so the chief of staff can't staff a role the business file doesn't declare (Jason)",
"id": 332,
"index": 172,
"inverse": "mark done: POST /api/v1/tasks/332 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
}
],
"problems": []
}
@@ -0,0 +1,27 @@
{
"at": "2026-10-18T15:00:03.855Z",
"project": 19,
"created": [
{
"title": "seat: a sandboxed launch hides a run directory under /tmp (#1657)",
"id": 334,
"index": 174,
"inverse": "mark done: POST /api/v1/tasks/334 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
},
{
"title": "cli: README lists project-not-found, but the resolver's line reaches the user first (#1658)",
"id": 335,
"index": 175,
"inverse": "mark done: POST /api/v1/tasks/335 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
}
],
"problems": []
}
@@ -0,0 +1,17 @@
{
"at": "2026-10-18T12:52:54.615Z",
"project": 19,
"created": [
{
"title": "harness: a reviewer without bash can't read the ref fetch_review fetched (blocks acceptance 314 step 4)",
"id": 331,
"index": 171,
"inverse": "mark done: POST /api/v1/tasks/331 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
}
],
"problems": []
}
@@ -0,0 +1,17 @@
{
"at": "2026-10-18T16:50:55.358Z",
"project": 19,
"created": [
{
"title": "webui: a cold load of a docs URL reads the project file twice (found in row 154, #1648)",
"id": 346,
"index": 186,
"inverse": "mark done: POST /api/v1/tasks/346 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
}
],
"problems": []
}
@@ -0,0 +1,17 @@
{
"at": "2026-10-18T21:32:25.395Z",
"project": 19,
"created": [
{
"title": "Role instances that bind credentials: add a reviewer, coder, pm or cto instance through the command (row 165 follow-up, Q171)",
"id": 367,
"index": 207,
"inverse": "mark done: POST /api/v1/tasks/367 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
}
],
"problems": []
}
@@ -0,0 +1,20 @@
{
"at": "2026-10-19T19:15:14.859Z",
"task": 380,
"title": "A tester message to a role nobody runs stays queued and reaches its next launch (window 8e7ea89a1 F3)",
"problems": [],
"comment": {
"status": 201,
"id": 281,
"readBack": 200,
"found": true
},
"update": {
"status": 200,
"inverse": "mark undone: POST /api/v1/tasks/380 done false (sage key)",
"readBack": 200,
"done": true,
"titleSame": true,
"descriptionSame": true
}
}
@@ -0,0 +1,20 @@
{
"at": "2026-10-19T19:33:30.195Z",
"task": 379,
"title": "Two project registries: the bus store's projects table and the business file's projects map share names (window 8e7ea89a1 F2)",
"problems": [],
"comment": {
"status": 201,
"id": 282,
"readBack": 200,
"found": true
},
"update": {
"status": 200,
"inverse": "mark undone: POST /api/v1/tasks/379 done false (sage key)",
"readBack": 200,
"done": true,
"titleSame": true,
"descriptionSame": true
}
}
@@ -0,0 +1,20 @@
{
"at": "2026-10-19T20:38:31.528Z",
"task": 366,
"title": "webui test: board-held.test.mjs:163 sign-out wait times out under suite load (twice on 2026-10-18)",
"problems": [],
"comment": {
"status": 201,
"id": 284,
"readBack": 200,
"found": true
},
"update": {
"status": 200,
"inverse": "mark undone: POST /api/v1/tasks/366 done false (sage key)",
"readBack": 200,
"done": true,
"titleSame": true,
"descriptionSame": true
}
}
@@ -0,0 +1,17 @@
{
"at": "2026-10-18T13:10:29.058Z",
"project": 19,
"created": [
{
"title": "install: a host process under another uid that reaches the Console port acts as Jason; accept it or fix it (Jason, row 87)",
"id": 333,
"index": 173,
"inverse": "mark done: POST /api/v1/tasks/333 done true (pm); delete is Jason's or ops-01's",
"readBack": 200,
"project_back": 19,
"due_back": "0001-01-01T00:00:00Z",
"done": false
}
],
"problems": []
}
@@ -0,0 +1,11 @@
// Sage, 2026-10-09: one read-only authenticated call after ops-01 deleted user "unnerving" (id 1).
// Uses the tracker adapter's own client (bearer token, /api/v2). Prints time, status and count only.
// Usage: node read-check.mjs BASE_URL TOKEN_FILE PROJECT_ID
import { readFileSync } from "node:fs";
import { client, all } from "../../../../packages/tasks/src/vikunja.mjs";
const [BASE, FILE, P] = process.argv.slice(2);
const token = readFileSync(FILE, "utf8").trim();
const call = client({ baseUrl: BASE });
const at = new Date().toISOString();
const res = await all(call, token, `/projects/${P}/tasks`);
console.log(`at=${at} status=${res.r?.status} ok=${res.ok} tasks=${res.items.length}`);
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T02:21:04.951Z",
"task": 386,
"comment": {
"status": 201,
"id": 324,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,20 @@
{
"at": "2026-10-20T02:21:08.562Z",
"task": 382,
"title": "An activate past c216f47db fails its own gate: the --pm chief launch refuses harness-off before Jason can switch claude-code on (row 65, #1558)",
"problems": [],
"comment": {
"status": 201,
"id": 325,
"readBack": 200,
"found": true
},
"update": {
"status": 200,
"inverse": "mark undone: POST /api/v1/tasks/382 done false (sage key)",
"readBack": 200,
"done": true,
"titleSame": true,
"descriptionSame": true
}
}
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T06:37:28.912Z",
"task": 386,
"comment": {
"status": 201,
"id": 346,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,11 @@
{
"at": "2026-10-20T07:18:12.740Z",
"task": 386,
"comment": {
"status": 201,
"id": 349,
"readBack": 200,
"found": true
},
"problems": []
}
@@ -0,0 +1,20 @@
{
"at": "2026-10-20T07:14:58.485Z",
"task": 403,
"title": "Row 185: every stack-release.sh activate backs up the stopped store (#1683)",
"problems": [],
"comment": {
"status": 201,
"id": 347,
"readBack": 200,
"found": true
},
"update": {
"status": 200,
"inverse": "mark undone: POST /api/v1/tasks/403 done false (sage key)",
"readBack": 200,
"done": true,
"titleSame": true,
"descriptionSame": true
}
}
@@ -0,0 +1,26 @@
{
"at": "2026-10-20T06:34:55.723Z",
"project": 19,
"created": {
"title": "Row 185: every stack-release.sh activate backs up the stopped store (#1683)",
"status": 201,
"id": 403,
"inverse": "mark done: POST /api/v1/tasks/403 done true (sage key); delete is Jason's or ops-01's",
"readBack": 200,
"done": false
},
"comment386": {
"status": 201,
"id": 344,
"readBack": 200,
"found": true
},
"comment385": {
"status": 201,
"id": 345,
"readBack": 200,
"found": true
},
"problems": [],
"title385": "Console trusts X-authentik-username from 127.0.0.1: any local process on dragon-lin (agents included) or a hostNetwork pod on k8s-woltje can act as jason.woltje"
}

Some files were not shown because too many files have changed in this diff Show More