sage: row 163 landing record, P19 comments 372/373 on 337/412, and P19 #413 filed for the --no-send row

This commit is contained in:
2026-10-20 13:32:55 -05:00
parent 1a268249e6
commit 59475fc5a8
4 changed files with 144 additions and 0 deletions
@@ -0,0 +1,56 @@
// Sage, 2026-10-20: row 163 (D2, #1660) round 2 approved. One comment on P19 337 (D2), per mos-0882 (every
// landing or decision gets a Vikunja comment). Sage's own key (bot user 13); the sync key reads back. Comments only:
// no task edits, labels, assignees or bucket moves. Checks the task's title before posting. Prints no secret.
// Usage: node comment-337-r163-approved.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 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 = (xs) => xs.map((x) => `<p>${esc(x)}</p>`).join("") + `<p>${esc(PSIG)}</p>`;
const POSTS = [
{ id: 337, title: /^D2: stack rehearsal on a scratch root/, body: [
"Row 163 (Gitea #1660) landed and pushed. Landing commit 8ecc7a239 (Sage; Filbert approve 29447), queue revs 2235-2236 (landing note and done) in 9ae8c73ac, range 4ca8d6e94..9ae8c73ac on refactor. Patch-id b8d5f8a37c24 matches candidate c6f4f4c12596. #1660 is closed with comment 29450.",
"Sage gate passed at 18:16Z on eaa0f2666: cli 472/472 and every package and scripts/test-*.sh green. The pushed commit is a cherry-pick of eaa0f2666 onto 7f57419b3, which adds only the Q175 design doc, and no test reads that file. Mos dropped the Part B wait when Q175 replaced Part B (mos-0971).",
"Not live. The live stack still runs copies/490e2771d3d0. Rehearse and bus start --no-launch go live at the next re-pin, where the Q173 backup and STATE report apply.",
] },
{ id: 412, title: /^Console SSO: zero-trust OIDC verification/, body: [
"Design r1 is committed: agents/sage/work/q175-sso/DESIGN.md in 7f57419b3 (blob 91e5b1b5e737), pushed in 9ae8c73ac. The Console becomes an OIDC relying party on oauth4webapi 3.8.8, pinned exactly. It always calls validateApplicationLevelSignature, because the library skips the signature check on a token-endpoint ID token. Identity is an (iss, sub) pair, and only asymmetric algs are accepted.",
"Filbert's r1 review asks for changes in the doc before any code (B1-B6). B1 moves the provider config to config.json, because agents have write paths into the business file. B2 closes an open-redirect hole. B3 keeps the library's error causes out of logs. B4 caps login records. B5 fixes the signature mutant test. B6 removes a live forged-header probe from the phase 2 acceptance. Their rulings: Q-B start degraded when the IdP is unreachable, Q-C yes (7332 accepts only the public Host under --sso), Q-D keep idle expiry but don't refresh it on poll GETs. r2 follows.",
"Interim (mos-0972): the Console runs without --send until phase 2. stack-release.sh has no way to drop --send that keeps stack-active.json and the unit in step, so a small tool row comes first, through 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 : []);
const rec = { at: new Date().toISOString(), comments: [], problems: [] };
const save = () => {
const out = JSON.stringify(rec, null, 1) + "\n";
if (out.includes(key) || out.includes(sync)) throw new Error("secret in the record; not written");
writeFileSync(OUT, out);
};
for (const P of POSTS) {
const t = await api("GET", `/api/v2/tasks/${P.id}`, undefined, sync);
if (t.status !== 200 || t.json?.project_id !== PROJECT || !P.title.test(t.json?.title ?? "")) throw new Error(`task ${P.id}: ${t.status}, title check failed; nothing posted`);
}
writeFileSync(OUT, "", { flag: "wx" });
for (const P of POSTS) {
const c = await api("PUT", `/api/v1/tasks/${P.id}/comments`, { comment: ps(P.body) }, key);
const e = { task: P.id, status: c.status, id: c.json?.id ?? null };
rec.comments.push(e);
if (c.status >= 300 || !Number.isSafeInteger(c.json?.id)) { rec.problems.push(`comment ${P.id}: ${c.status} ${c.json?.message ?? ""}`); save(); continue; }
const back = await api("GET", `/api/v2/tasks/${P.id}/comments?per_page=50`, undefined, sync);
const found = list(back.json).find((x) => x.id === c.json.id);
Object.assign(e, { readBack: back.status, found: Boolean(found) });
if (back.status !== 200 || !found) rec.problems.push(`comment ${P.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,56 @@
// Sage, 2026-10-20: files the activate --no-send row (mos-0972, mos-0973) as one P19 task, owner Sage, no due date.
// Sage's own key (bot user 13); the sync key reads back. No labels, assignees, due dates or bucket moves. Refuses if
// the title exists. Prints no secret.
// Usage: node file-no-send-row.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 ps = (sig, xs) => xs.map((x) => `<p>${esc(x)}</p>`).join("") + `<p>${esc(sig)}</p>`;
const T = {
title: "stack-release.sh activate --no-send: drop the Console's --send and keep the pointer in step (Q175 interim, mos-0972)",
description: ps(SIG, [
"Mos ruled (mos-0972) that the Console runs without --send until Q175 phase 2 (P19 #412), because with --send live any same-uid process can forge X-authentik-username on 127.0.0.1:7332 and send as Jason.",
"stack-release.sh has no path that drops --send and keeps ~/.mosaic-dev/state/stack-active.json and the unit in step: console_for_target (:658-687) carries the pointer's flags, check_target (:247-249) allows a same-copy activate only for the proof pair, and adopt refuses once a pointer exists. A hand reinstall makes flags_drift refuse every later activate, pm-on and rollback.",
"The row adds activate --no-send, modeled on row 187's --no-proof. It drops --send from the pointer and the unit. A same-copy activate is allowed when the pointer holds --send. Rollback and pm-on carry the pointer as they do now. There is no re-enable flag. Brief: docs/plans/2026-10-20_activate-no-send.md. Owner Sage, reviewer Dewey, gate the cli suite plus every scripts/test-*.sh (mos-0973 go).",
"After landing: a same-copy activate on 490e2771d3d0 with --no-send under Q173, with the plan output sent to Mos first, a backup first and a STATE report after. Then row 66 lands.",
]),
};
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, 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);
};
if ((await existing()).some((t) => t.title === T.title)) throw new Error("title exists; refusing");
writeFileSync(OUT, "", { flag: "wx" });
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, { title: T.title, description: T.description }, key);
rec.created = { 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 {
const back = await api("GET", `/api/v2/tasks/${r.json.id}`, undefined, sync);
Object.assign(rec.created, { readBack: back.status, done: back.json?.done, inverse: `mark done: POST /api/v1/tasks/${r.json.id} with the whole task, done true (sage key); delete is Jason's or ops-01's` });
if (back.status !== 200 || back.json?.title !== T.title || back.json?.done) rec.problems.push("create read-back differs");
}
save();
console.log(JSON.stringify(rec));
@@ -0,0 +1,12 @@
{
"at": "2026-10-20T18:21:15.323Z",
"project": 19,
"created": {
"status": 201,
"id": 413,
"readBack": 200,
"done": false,
"inverse": "mark done: POST /api/v1/tasks/413 with the whole task, done true (sage key); delete is Jason's or ops-01's"
},
"problems": []
}
@@ -0,0 +1,20 @@
{
"at": "2026-10-20T18:19:36.544Z",
"comments": [
{
"task": 337,
"status": 201,
"id": 372,
"readBack": 200,
"found": true
},
{
"task": 412,
"status": 201,
"id": 373,
"readBack": 200,
"found": true
}
],
"problems": []
}