decisions, build-log, sessions: 110, Q109 follow-ups filed in Vikunja P19 (#153-#161)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"at": "2026-10-12T17:37:55.750Z",
|
||||
"project": 19,
|
||||
"created": [
|
||||
{
|
||||
"title": "I1 brief: containerize managed sessions by role (decisions 105-108)",
|
||||
"id": 153,
|
||||
"index": 39,
|
||||
"inverse": "mark done: POST /api/v1/tasks/153 done true (pm); delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"project_back": 19,
|
||||
"due_back": "2026-10-13T22:00:00Z",
|
||||
"done": false
|
||||
},
|
||||
{
|
||||
"title": "M1 brief and queue row: chief-of-staff role (decision 104)",
|
||||
"id": 154,
|
||||
"index": 40,
|
||||
"inverse": "mark done: POST /api/v1/tasks/154 done true (pm); delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"project_back": 19,
|
||||
"due_back": "2026-10-13T22:00:00Z",
|
||||
"done": false
|
||||
},
|
||||
{
|
||||
"title": "Row 77 brief: Console service hardening (#1571)",
|
||||
"id": 155,
|
||||
"index": 41,
|
||||
"inverse": "mark done: POST /api/v1/tasks/155 done true (pm); delete is Jason's or ops-01's",
|
||||
"readBack": 200,
|
||||
"project_back": 19,
|
||||
"due_back": "2026-10-13T22:00:00Z",
|
||||
"done": false
|
||||
},
|
||||
{
|
||||
"title": "Narrow the pi worker's dataRoot mount (compose.yaml:40)",
|
||||
"id": 156,
|
||||
"index": 42,
|
||||
"inverse": "mark done: POST /api/v1/tasks/156 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": "Stale docs: LAYERS.md L4 and CONDUCTOR.md lines 17 and 40",
|
||||
"id": 157,
|
||||
"index": 43,
|
||||
"inverse": "mark done: POST /api/v1/tasks/157 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": "Board Reply needs a delivery path that isn't tmux",
|
||||
"id": 158,
|
||||
"index": 44,
|
||||
"inverse": "mark done: POST /api/v1/tasks/158 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": "Console cosmetic follow-ups from rows 76 and 83",
|
||||
"id": 159,
|
||||
"index": 45,
|
||||
"inverse": "mark done: POST /api/v1/tasks/159 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": "Console flag tests: allowed user starting with \"-\", and the [--send] usage text",
|
||||
"id": 160,
|
||||
"index": 46,
|
||||
"inverse": "mark done: POST /api/v1/tasks/160 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": "Row 67 test 4: assert its new decisions are listed (Dewey N1)",
|
||||
"id": 161,
|
||||
"index": 47,
|
||||
"inverse": "mark done: POST /api/v1/tasks/161 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,82 @@
|
||||
// Sage, 2026-10-12: Q109 ("Always use Vikunja for task tracking"). Files Sage's open Mosaic follow-ups
|
||||
// as tasks in project 19 with the pm token slot, the way Mos filed #146 to #152. Refuses if any title
|
||||
// already exists in project 19. The sync key reads each task 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-followups.mjs https://tasks.woltje.com PM_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, DUE = "2026-10-13T22:00:00Z";
|
||||
const SIG = "Created by Sage (T3 1ef1e4f8) with the pm token slot until Sage has its own key.";
|
||||
const p = (...xs) => xs.map((x) => `<p>${x}</p>`).join("") + `<p>${SIG}</p>`;
|
||||
const TASKS = [
|
||||
{ title: "I1 brief: containerize managed sessions by role (decisions 105-108)", due: DUE,
|
||||
description: p("Sage writes the I1 brief on BRIEF-TEMPLATE.md from agents/sage/work/next-target/I1-review.md: two broker sockets with the human one only in the Console, a new via value in authenticateHuman, narrow per-run mounts, cap_drop ALL and limits, an internal network with one egress proxy, the rootless endpoint pinned with a refusal unless docker info lists name=rootless, and the subscription token as a read-only file secret. The survey's unverified items go in as spikes with tests. Owner Sage.") },
|
||||
{ title: "M1 brief and queue row: chief-of-staff role (decision 104)", due: DUE,
|
||||
description: p("Move agents/sage/work/next-target/M1-draft.md to docs/plans on BRIEF-TEMPLATE.md and open its queue row: owner Rocko, reviewers Darkwing and Filbert, gate Sage. The row ships roles/chief-of-staff.json and .md, fixture tests in packages/business/tests and the documented instance entry; it never writes Jason's business file. The instance name is #150. Owner Sage.") },
|
||||
{ title: "Row 77 brief: Console service hardening (#1571)", due: DUE,
|
||||
description: p("Write the brief for queue row 77 (Gitea #1571, owner Rocko), held until after the cutover, which is done (#152). Coordinate with #146 and #147, since all three change how the mosaic-webui unit runs. Owner Sage.") },
|
||||
{ title: "Narrow the pi worker's dataRoot mount (compose.yaml:40)",
|
||||
description: p("compose.yaml:40 mounts the whole dataRoot read-write into the pi worker as uid 1000, which includes bus/broker.sock. Narrow it to the worker's own run and workspace paths. This exposure exists today and doesn't wait on I1. Found in Sage's I1 review. Needs a queue row and brief.") },
|
||||
{ title: "Stale docs: LAYERS.md L4 and CONDUCTOR.md lines 17 and 40",
|
||||
description: p("LAYERS.md L4 and docs/plans/CONDUCTOR.md:17,40 say managed sessions run in containers. They run on the host today. Correct the docs, and point at I1 for the containerized design. Found in Sage's I1 review.") },
|
||||
{ title: "Board Reply needs a delivery path that isn't tmux",
|
||||
description: p("The control board's /api/reply pastes into a tmux pane, which can't cross a container boundary. It needs another delivery path before the board or the sessions containerize under I1. Found in Sage's I1 review.") },
|
||||
{ title: "Console cosmetic follow-ups from rows 76 and 83",
|
||||
description: p("Small fixes left after the cutover:",
|
||||
"1. packages/webui/systemd/mosaic-webui.service.in lines 6-9 still say Jason starts the unit and cite decisions 62 and 94; decision 102 says Jason or Mos.",
|
||||
"2. packages/webui/README.md line 106 is 102 characters; wrap it.",
|
||||
"3. Row 76: Darkwing's spliced header-comment sentence at packages/webui/tests/send-browser.test.mjs line 4, and Filbert's two round 2 notes (Gitea #1570 comment 27685).",
|
||||
"4. When a session has expired, the proxy redirects a Reply to sign-in, the fetch fails without a status, and the page says \"Delivery may be unknown\" (packages/webui/src/public/app.js:380). Nothing reached the Console, so it should say not sent and to sign in again.",
|
||||
"Touches packages/webui, so decision 109's notice to Mos applies before landing.") },
|
||||
{ title: "Console flag tests: allowed user starting with \"-\", and the [--send] usage text",
|
||||
description: p("From the row 83 landing (1a8ba587): an --allow-user value beginning with \"-\" needs a decision and a test in both scripts/webui-service.sh and packages/webui/src/serve.mjs, kept consistent. Darkwing's review (#1576 comment 27721) left a surviving mutant on the [--send] usage text; add a test that kills it. Touches packages/webui, so decision 109's notice to Mos applies before landing.") },
|
||||
{ title: "Row 67 test 4: assert its new decisions are listed (Dewey N1)",
|
||||
description: p("Dewey's N1 on row 67 (#1560 comment 27452), which Darkwing repeated (27627): test 4 creates decisions but doesn't assert that the listing shows them. Add the assertion.") },
|
||||
];
|
||||
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);
|
||||
Reference in New Issue
Block a user