ledger: weekly run 2026-10-04 to 2026-10-10, issue call refused; P19 #252 (#1508)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-13 08:08:54 -05:00
co-authored by Claude Opus 5.5
parent ba7553ec7a
commit 2b75ef4183
3 changed files with 144 additions and 0 deletions
@@ -0,0 +1,63 @@
Weekly ledger run by Sage, 2026-10-13, week 2026-10-04 to 2026-10-10 (row 7, #1508). Due Monday 2026-10-12; it ran a day late.
Seats declared with --unsupported-runtime: darkwing, dewey, filbert, researcher, rocko, sage.
== Run 1, with issues, 2026-10-13T13:07:52Z, exit 2
Issues unavailable: full 50-row page may be incomplete; one-call limit forbids pagination. Narrow --since or use --no-issues
== Run 2, --no-issues, 2026-10-13T13:08:19Z, exit 0
Queue checks: queue.json revision 892, as of 2026-10-13T13:08:19.302Z
violation age row 13: row 13 "Ledger "queue" section: issue/row/seat drift printed with the weekly number" is required and not done, 30 days since 2026-09-13
undecided issues-not-run: the issue checks did not run (--no-issues)
owner darkwing (rows 13, 91): exempt, declared with --unsupported-runtime
owner researcher (rows 71, 80, 84, 88, 97, 98, 99): exempt, declared with --unsupported-runtime
owner sage (rows 7, 42): exempt, declared with --unsupported-runtime
liveness: 0 pid-present (unverified), 3 exempt, 0 pid-unknown, 0 missing, 0 invalid, 0 pid-gone
declared unsupported runtime: darkwing, dewey, filbert, researcher, rocko, sage
queue issue checks: not run
protected change rev 32 move by sage at 2026-10-04T05:04:12.580Z: row 9
protected change rev 33 move by sage at 2026-10-04T05:04:12.696Z: row 10
protected change rev 35 note by sage at 2026-10-04T05:04:12.925Z: row 11
protected change rev 55 move by jason at 2026-10-04T18:59:33.100Z: row 32
protected change rev 122 move by sage at 2026-10-05T21:43:41.213Z: row 11
protected changes in range: 5 (not checks; confirm the actors)
queue: 1 violation; result fail
Ledger 2026-10-04 through 2026-10-10 UTC
Issue | Title | Opened | Hours open | Commits | Follow-ups | Seats
#1507 | unknown | unknown | unknown | 4 | 3 | darkwing, dewey, filbert, sage
#1508 | unknown | unknown | unknown | 19 | 18 | darkwing, dewey, filbert, sage
#1510 | unknown | unknown | unknown | 2 | 1 |
#1514 | unknown | unknown | unknown | 2 | 1 | darkwing
#1516 | unknown | unknown | unknown | 4 | 3 | darkwing, filbert, sage
#1517 | unknown | unknown | unknown | 5 | 4 | darkwing, sage
#1518 | unknown | unknown | unknown | 4 | 3 | darkwing, filbert, rocko, sage
#1519 | unknown | unknown | unknown | 2 | 1 | darkwing, rocko, sage
#1520 | unknown | unknown | unknown | 1 | 0 | darkwing, filbert, rocko, sage
#1521 | unknown | unknown | unknown | 1 | 0 | darkwing, filbert, rocko, sage
#1522 | unknown | unknown | unknown | 2 | 1 | darkwing, dewey, filbert, sage
#1525 | unknown | unknown | unknown | 4 | 3 | darkwing, rocko, sage
#1526 | unknown | unknown | unknown | 3 | 2 | darkwing, dewey, rocko, sage
#1527 | unknown | unknown | unknown | 8 | 7 | darkwing, filbert, rocko, sage
#1528 | unknown | unknown | unknown | 2 | 1 | darkwing, dewey, sage
#1533 | unknown | unknown | unknown | 2 | 1 | darkwing, dewey, filbert, sage
#1534 | unknown | unknown | unknown | 1 | 0 | darkwing, dewey, filbert, sage
#1535 | unknown | unknown | unknown | 1 | 0 |
#1536 | unknown | unknown | unknown | 2 | 1 | darkwing, dewey, filbert, sage
#1537 | unknown | unknown | unknown | 3 | 2 | darkwing, dewey, filbert, sage
#1538 | unknown | unknown | unknown | 3 | 2 | darkwing, dewey, filbert, sage
#1542 | unknown | unknown | unknown | 13 | 12 | darkwing, dewey, filbert, sage
#1543 | unknown | unknown | unknown | 1 | 0 | darkwing, dewey, filbert, sage
#1545 | unknown | unknown | unknown | 9 | 8 | darkwing, filbert, rocko, sage
#1552 | unknown | unknown | unknown | 1 | 0 |
#1554 | unknown | unknown | unknown | 1 | 0 | darkwing, dewey
Seat | Board | Agent | Human
darkwing | 0 | 77 | 20
dewey | 0 | 46 | 25
filbert | 0 | 46 | 22
researcher | 0 | 2 | 0
rocko | 0 | 51 | 4
sage | 0 | 149 | 49
t3:unmapped | 0 | 0 | 1
Totals: issues closed unknown | median hours open unknown | commits 96 | follow-ups per issue 2.8 | human messages per closed issue unknown
@@ -0,0 +1,17 @@
{
"at": "2026-10-13T13:08:44.555Z",
"project": 19,
"created": [
{
"title": "Ledger issue query needs an upper bound: weekly run refuses on a full page",
"id": 252,
"index": 95,
"inverse": "mark done: POST /api/v1/tasks/252 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,64 @@
// Sage, 2026-10-13: decision 110. Files the ledger issue-query bound (row 7, #1508) 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-ledger-before.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 issue query needs an upper bound: weekly run refuses on a full page",
description: p("Row 7's run for 2026-10-04 to 2026-10-10 (2026-10-13) exited 2: the issue call returned a full 50-row page. packages/ledger/src/ledger.mjs:52 asks Gitea for issues updated since the start date with no upper bound, so every issue touched after the week also counts toward the 50. With the volume since 2026-10-04, no past week can run with issues.",
"Fix: pass Gitea's before parameter at the end of the range, so the one call covers the week only, and test it with the fake gitea-api.sh. Note that an issue closed in the week and updated again later drops out of an updated-before window, so check whether close-only rows need a second bounded call and say so in the README.",
"Until it lands, the weekly number (human messages per closed issue) can't be computed, and row 13's gate (a Monday run with zero violations) can't be met, because --no-issues skips the queue issue checks. Run record: agents/sage/work/ledger/2026-10-13_weekly.txt. Needs a queue row and brief; owner Sage to assign.") },
];
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);