diff --git a/BUILD-LOG.md b/BUILD-LOG.md index 7d7b79b18..6ad2249ce 100644 --- a/BUILD-LOG.md +++ b/BUILD-LOG.md @@ -4474,3 +4474,11 @@ After (b8539480): Darkwing's round 2 candidate, manifest 2a22fd3f, six paths und Before (c6711453): the terminal app's narrow-terminal guard had no test, a hidden input box left no way out, and the width cases were untested. After (90d7ed66): Darkwing's round 1 candidate, manifest c68e6582, four paths under `packages/cli/`. Dewey approved in comment 28048 and Filbert in 28055. Sage's gate on c6711453 plus the patch ran 23:34:36Z to 23:36:40Z: cli 123/0, queue 150/0, and every `scripts/test-*.sh` green. No commit since base dad20531 touched the four paths, and `queue review verify-commit` matched the candidate. Row 69 went to done at rev 857 (7f0f54ab), and #1562 is closed with comment 28061. Mos had the decision-109 notices before and after; the Console and bus were active with NRestarts 0 and healthz 200 afterwards, and nothing restarted. The help-pointer line and Dewey's two surviving mutant findings are Vikunja #243 (decision 110). A correction for the record: my gate's busy check counted only node and chromium processes, and it passed in a gap between Darkwing's row 91 mutants (23:32:16Z to 23:35:26Z), so the two runs overlapped. Both sets of results stand: mine were green, and Darkwing explained every mutant result. On the strength of that check I also gave Filbert the slot early, and Filbert's row 90 run stopped after tasks when Darkwing's suites started. Filbert reruns row 90 from the start after Darkwing's "slot clear" (23:39:25Z). + +### 2026-10-12 — Sage, row 90 landed: the approved requirement ids as data (#1584) + +Before (c047d028): `task_create` took any string shaped like a requirement id, and a PM session had no way to see the approved ones. + +After (e918a915): Dewey's round 2 candidate, manifest 95611274, 21 paths under `packages/tasks/`, `packages/bus/`, `packages/harness/` and `packages/seat/`, plus `roles/pm.md`. The approved PRD's 24 ids live in `packages/tasks/requirements.json`. `task.create` refuses an id outside them with `unknown-requirement`, and sessions that hold `task.create` get a `list_requirements` tool. Round 2 rebased on row 63 and added `packages/tasks/src/requirements.mjs` to the sandbox's `RUNNER_FILES` (decision 127). Filbert approved round 2 in comment 28068 after a full rerun. Sage's gate on c047d028 plus the patch ran 23:50:16Z to 23:54:26Z with Docker disabled: tasks 89, bus 111, harness 66, webui 101, cli 123, seat 33, business 60, control-board 124, queue 150, conversation 182, discord 178, ledger 78, runs 43, mosaic 69, all fail 0, and every `scripts/test-*.sh` exited 0. No commit since base b8539480 touched the 21 paths, and `queue review verify-commit` matched. Row 90 went to done at rev 863 (e2fe22fc), and #1584 is closed with comment 28078. Filbert's three round 1 follow-ups are Vikunja #245. After landing, the Console and bus were active, NRestarts 0, healthz 200, and nothing restarted. Row 94 goes to round 2 rebased on this commit, because both rows change `packages/bus/README.md` and `packages/bus/src/broker.mjs` (Mos: whichever is approved first lands first). Darkwing's launcher `recover()` finding is decision 128 and Vikunja #244. + +Two corrections for the record. First, I proposed gating rows 94 and 90 stacked and landing both. That can't work: each manifest pins its own version of the two shared files, so the second landing would fail `verify-commit`. Row 94 takes a round 2 instead. Second, my decision-109 notice told Mos that nothing went live until the bus restart. The running bus (started 18:53:34Z, before rows 89 and 92) starts each role session as a fresh `runner.mjs` from the checkout, so a session started before the restart would get row 90's harness against the old broker. I corrected it before landing. Mos confirmed only the PM session was running and none would start (coder and reviewer launches have been off since Q125), and said to land. From now on, decision-109 notices will say when the harness changes ahead of a bus restart. diff --git a/agents/sage/work/vikunja-setup/2026-10-12_r90-followup.json b/agents/sage/work/vikunja-setup/2026-10-12_r90-followup.json new file mode 100644 index 000000000..348365e4e --- /dev/null +++ b/agents/sage/work/vikunja-setup/2026-10-12_r90-followup.json @@ -0,0 +1,17 @@ +{ + "at": "2026-10-12T23:57:20.413Z", + "project": 19, + "created": [ + { + "title": "Requirement ids follow-up from row 90: inherited keys, all 24 ids, carriage return", + "id": 245, + "index": 92, + "inverse": "mark done: POST /api/v1/tasks/245 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-r90-followup.mjs b/agents/sage/work/vikunja-setup/file-r90-followup.mjs new file mode 100644 index 000000000..2e6872beb --- /dev/null +++ b/agents/sage/work/vikunja-setup/file-r90-followup.mjs @@ -0,0 +1,65 @@ +// Sage, 2026-10-12: decision 110. Files row 90's non-blocking follow-ups (#1584) 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-r90-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, "&").replace(//g, ">"); +const p = (...xs) => xs.map((x) => `

${esc(x)}

`).join("") + `

${esc(SIG)}

`; +const TASKS = [ + { title: "Requirement ids follow-up from row 90: inherited keys, all 24 ids, carriage return", + description: p("Row 90 (Gitea #1584) landed as e918a915. Filbert's review (comment 28021, reproducers in agents/filbert/work/queue-90/r1/) left three non-blocking items. The code is correct today in each case. Two are test gaps a mutant survives, and one is a validation gap.", + "1. packages/tasks/tests/requirements.test.mjs has no inherited-key case. Replacing Object.hasOwn with in survives. Add refusals for an inherited key at the top level and at entry level.", + "2. packages/tasks/tests/verbs.test.mjs checks only the first and last approved ids. An allowlist of just those plus REQ-TASK-1 survives. Loop through all 24 ids.", + "3. checkRequirements in packages/tasks/src/requirements.mjs accepts a title containing a carriage return, though the schema says one line. Reject it as well as newline, with that boundary test. The committed titles contain neither.") }, +]; +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 b8db2f079..6f64b178a 100644 --- a/docs/SESSIONS.md +++ b/docs/SESSIONS.md @@ -824,3 +824,4 @@ are never rewritten or removed; corrections are new entries. 2026-10-12T23:45:33Z | darkwing | row 91 (#1585) round 1 packaged | rebased onto c974cafa past row 69's cli README change, packet f759432e, manifest 39d6eb7d, in review rev 860 (9d4decca), request comment 28067 to Filbert; row 90 r2 patch applies in both orders 2026-10-12T23:48:19Z | filbert | row 90 (#1584) round 2 review on 95611274, base b8539480 | approve comment 28068, rev 861 committed d199c51f; full quiet restart all 8 node and 9 shell suites rc=0, harness 66/0, cli 120/0, seat 33/0; Docker disabled (release 4/task 26, Docker cases skipped), manifest 21/21 and no drift; partial slot attempt preserved separately; scratch/worktree clear, Dewey notified for row 94; no push 2026-10-12T23:51:34Z | dewey | row 94 round 1 review (#1588) on bd9e0cea: bus 112, cli 125, webui 103 green; my 8 mutants 1 killed, 7 survived (test gaps); verdict changes, comment 28075, rev 862 503d7ae9, packet 2f0b52fa; r2 rebases on row 90 (Mos's ruling) | changes requested +2026-10-12T23:57:54Z | sage | row 90 landing (#1584), decision 128 | landed e918a915, rev 863, #1584 closed 28078; gate green on 14 node and 9 scripts suites; decision 128, launcher recover() gap is Vikunja #244; row 90 follow-ups Vikunja #245; row 94 to round 2 on e918a915; corrected my stacked-gate plan and the live-harness wording in my Mos notice