diff --git a/BUILD-LOG.md b/BUILD-LOG.md index ca810fb93..94b086cd7 100644 --- a/BUILD-LOG.md +++ b/BUILD-LOG.md @@ -4388,3 +4388,9 @@ After (1a8ba587): Dewey's round 1, 4 files +202 -22. render and install take `-- Before: mosaic.woltje.com served the old build, and the Console unit (installed 04:14:24Z from 777ada59) was installed for mosaic-preview.woltje.com, disabled and inactive. After: Jason gave the go at 17:21:09Z. Mos reports that ops-01 switched Argo `mosaic-preview` to `overlays/cutover` (Synced and Healthy at ebfd7065 from 17:21:32Z). Mos reinstalled the unit for mosaic.woltje.com under decision 103 and started it at 17:21:41Z, PID 3951396, from 6040d424, which has no webui diff from 36e34e5b. Their checks: anonymous LAN and external requests and forged headers get 302 to Authentik. Directly through the tunnel, the Console returns 401 with no identity and 403 for a non-local Host (ops-01, 17:22Z). Jason reached the dashboard from the WAN. Sage's read-only check at 17:27Z: `scripts/webui-service.sh status` enabled, active, NRestarts 0, "send: on", /healthz 200; ExecStart is decision 103's command. Undo: the Argo path back to `overlays/prod` and a reinstall with the preview host. Found while checking: the unit runs from the canonical checkout, and each request spawns human-cli from it, so landings there are live changes. Decision 109 sets the landing rule until a follow-up row pins the Console's copy. + +### 2026-10-12 — Sage, follow-ups move to Vikunja (Q109, decision 110) + +Before: the follow-ups from rows 76, 83, 67 and the I1 review existed only as lists in BUILD-LOG entries, the standup and decision 109. Mos had filed #146 (decision 109's pinned Console copy) and #147 (decision 101's uid reinstall) in Vikunja project 19, both due 2026-10-13. + +After: Jason ruled "Always use Vikunja for task tracking" (Q109, relayed by Mos in sage-0081). At 17:37:55Z Sage filed nine tasks in project 19 on tasks.woltje.com with the pm token slot, each read back with the sync key: #153 to #155 (the I1, M1 and row 77 briefs, due 2026-10-13) and #156 to #161 (the open follow-ups). Evidence: `agents/sage/work/vikunja-setup/2026-10-12_followups.json` and `file-followups.mjs`. The follow-up lists in the row 83 and cutover entries above are now tracked as #159, #160 and #146. Later entries cite Vikunja ids instead of listing follow-ups. diff --git a/agents/sage/work/vikunja-setup/2026-10-12_followups.json b/agents/sage/work/vikunja-setup/2026-10-12_followups.json new file mode 100644 index 000000000..a81c24928 --- /dev/null +++ b/agents/sage/work/vikunja-setup/2026-10-12_followups.json @@ -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": [] +} diff --git a/agents/sage/work/vikunja-setup/file-followups.mjs b/agents/sage/work/vikunja-setup/file-followups.mjs new file mode 100644 index 000000000..16df07025 --- /dev/null +++ b/agents/sage/work/vikunja-setup/file-followups.mjs @@ -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) => `
${x}
`).join("") + `${SIG}
`; +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); diff --git a/docs/SESSIONS.md b/docs/SESSIONS.md index 6e54c08d2..f812fcdd8 100644 --- a/docs/SESSIONS.md +++ b/docs/SESSIONS.md @@ -752,3 +752,4 @@ are never rewritten or removed; corrections are new entries. 2026-10-12T14:59:20Z | sage | decision 107, I1-3 deferred | Jason deferred Q104 via Mos sage-0077: no WebAuthn in the I1 brief; reopens only as a new question once containers run; a compromised Console holding human authority is recorded as open 2026-10-12T15:00:59Z | sage | decision 108, I1-4 answered | Jason chose Q105 A via Mos sage-0078: subscription token as a read-only file secret per session container, no API key or gateway; copy-out risk accepted, bounded by the egress allowlist; all four I1 items answered, brief is Sage's on 2026-10-13 2026-10-12T17:27:49Z | sage | cutover live, decision 109 | verified the mosaic.woltje.com Console read-only after Mos's 17:21Z start (status active, send on, healthz 200); decision 109: landings touching webui, bus or cli in the canonical checkout are live changes until the Console runs from a pinned copy; BUILD-LOG entry +2026-10-12T17:38:19Z | sage | decision 110, follow-ups to Vikunja | Jason's Q109 via Mos sage-0081: Vikunja project 19 holds Mosaic work items; filed #153-#161 with the pm slot, read back with sync; #146 and #147 cited for the 10-13 rows; queue and Gitea stay the review and landing record; BUILD-LOG entry diff --git a/docs/plans/2026-09-26_lead-decisions.md b/docs/plans/2026-09-26_lead-decisions.md index c7eb96bb9..e64b7575c 100644 --- a/docs/plans/2026-09-26_lead-decisions.md +++ b/docs/plans/2026-09-26_lead-decisions.md @@ -2437,3 +2437,28 @@ which stay with him. Each item names who decided it and what happened. being deploys. Pointing the unit at it is a reinstall, which is Mos's step under decisions 102 and 103. It goes in the same window as decision 101's uid reinstall. +110. **Vikunja holds Mosaic work items (2026-10-12, Q109).** Jason, relayed + by Mos in sage-0081: "Always use Vikunja for task tracking." Mosaic + work items live as tasks in project 19 (Mosaic Stack) on + tasks.woltje.com. Sage files each new one there when raising it and + stops keeping follow-up lists in BUILD-LOG, standups or decisions. + Those records cite the Vikunja id. + - Filed at 17:37:55Z with the pm token slot, as Mos filed #146 to + #152, since Sage has no key of its own: #153 I1 brief, #154 M1 + brief and row, #155 row 77 brief (all three due 2026-10-13), #156 + compose.yaml:40 mount, #157 stale LAYERS and CONDUCTOR docs, #158 + board Reply without tmux, #159 Console cosmetic notes from rows 76 + and 83, #160 Console flag tests, #161 row 67 test 4 (Dewey N1). + Each was read back with the sync key. No labels, assignees or + bucket moves. Evidence: + `agents/sage/work/vikunja-setup/2026-10-12_followups.json`. + - Decision 109's pinned-copy row is #146 and decision 101's uid + reinstall is #147. Both are due 2026-10-13, and the 10-13 rows + cite them. + - The queue and its Gitea issues stay the review and landing + record, because `queue review` and `verify-commit` gate landings + on them. When a row opens for a Vikunja task, the task names the + row and the row's brief names the task. Moving the queue itself + into Vikunja would be a separate change and goes to Jason first. + - If the pm slot fails for Sage, Mos files the task (sage-0081). + Agent key scopes are #151, owned by ops-01.