feat(queue): queue as data A2, migration, render and dispatch (#1508)

Filbert approved round 1 (f167b85e). Manifest 782bcb62, 21 files, plus
the QUEUE.md markers and the TOOLS.md section. Lead decision 35.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-09-26 20:14:09 -05:00
co-authored by Claude Opus 5.5
parent c9539baa0f
commit 6ca116b7ba
32 changed files with 4886 additions and 103 deletions
+145 -15
View File
@@ -20,16 +20,16 @@ function brief(path = "docs/plans/brief-b.md", anchor = "Queue", blob = BLOB) {
}
// A genesis document built the way store.mjs builds one.
function genesisDoc(rows = MAP_ROWS) {
function genesisDoc(rows = MAP_ROWS, op = "genesis-op-0001") {
const map = parseMigrationMap(mapText(rows));
const blobs = new Map(map.rows.filter((r) => r.brief).map((r) => [r.id, BLOB]));
const t = at();
const grows = genesisRows(map, blobs, "genesis-op-0001", t, "sage");
const grows = genesisRows(map, blobs, op, t, "sage");
const body = render(grows, 0);
const entry = {
rev: 0, op: "genesis-op-0001", verb: "genesis", args: { root: ROOT, branch: "refactor", map: "agents/sage/work/queue-migration-map.md" },
rev: 0, op, verb: "genesis", args: { root: ROOT, branch: "refactor", map: "agents/sage/work/queue-migration-map.md" },
by: "sage", at: t, semantics: 1,
result: { mapBlob: BLOB2, highWater: map.highWater, retired: map.retired, rows: grows, legacyView: "legacy\n", receipt: genesisReceipt("genesis-op-0001", grows.length) },
result: { mapBlob: BLOB2, highWater: map.highWater, retired: map.retired, rows: grows, legacyView: "legacy\n", receipt: genesisReceipt(op, grows.length) },
viewSha: sha256(body),
};
return { version: 1, canonicalRoot: ROOT, revision: 0, rows: grows, log: [entry] };
@@ -154,8 +154,8 @@ test("matrix: release, review round, changes requested and waiting-on-jason", ()
refused(() => step(d, "move", mv(9, "in-review", { candidate: "x.sha256" }), "dewey", { candidate: CAND }), /only the claimant/);
d = step(d, "move", mv(9, "in-review", { candidate: "x.sha256" }), "darkwing", { candidate: CAND });
const rv = row(d, 9).review;
assert.equal(rv.issue, 1508);
assert.deepEqual(rv.rounds.map((r) => [r.n, r.request, r.candidate.digest]), [[1, "none", CAND.digest]]);
assert.deepEqual(Object.keys(rv), ["rounds"]);
assert.deepEqual(rv.rounds.map((r) => [r.n, r.issue, r.request, r.candidate.digest]), [[1, 1508, "none", CAND.digest]]);
assert.match(d.log.at(-1).result.receipt, /in-progress→in-review round 1 on #1508$/);
refused(() => step(d, "move", mv(9, "in-progress"), "dewey"), /claimed by darkwing/);
d = step(d, "move", mv(9, "in-progress"), "darkwing");
@@ -209,13 +209,15 @@ function row12Started(issues, { gateOwner = "filbert" } = {}) {
const review = (d, extra = {}) => step(d, "move", mv(12, "in-review", { candidate: "x", ...extra }), "darkwing", { candidate: CAND });
const again = (d) => step(d, "move", mv(12, "in-progress"), "darkwing");
// The issue the next round keeps: the last round's own (P2).
const kept = (d) => row(d, 12).review.rounds.at(-1).issue;
test("review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it", () => {
// No issues: refused before the round opens.
refused(() => review(row12Started([])), /row 12 lists no issues; a privileged actor sets one before review/);
// One issue: used without --issue; --issue may name it; any other refuses.
const one = review(row12Started([1508]));
assert.equal(row(one, 12).review.issue, 1508);
assert.equal(kept(one), 1508);
assert.match(one.log.at(-1).result.receipt, /round 1 on #1508$/);
assert.equal(one.log.at(-1).result.issue, 1508);
refused(() => review(row12Started([1508]), { issue: 1495 }), /--issue #1495 is not one of row 12's issues \(#1508\)/);
@@ -225,25 +227,36 @@ test("review issue, lead decision 23: none refuses, one is used, several need --
refused(() => review(several), /row 12 lists several issues \(#1495, #1508\); name the review's issue with --issue/);
refused(() => review(several, { issue: 1600 }), /--issue #1600 is not one of row 12's issues \(#1495, #1508\)/);
let d = review(several, { issue: 1508 });
assert.equal(row(d, 12).review.issue, 1508);
assert.equal(kept(d), 1508);
// Later rounds keep the previous round's issue unless --issue names another.
d = review(again(d));
assert.deepEqual([row(d, 12).review.issue, row(d, 12).review.rounds.length], [1508, 2]);
assert.deepEqual([kept(d), row(d, 12).review.rounds.length], [1508, 2]);
d = review(again(d), { issue: 1495 });
assert.deepEqual([row(d, 12).review.issue, row(d, 12).review.rounds.length], [1495, 3]);
assert.deepEqual([kept(d), row(d, 12).review.rounds.length], [1495, 3]);
// P2: each round keeps the issue it posted to; a later --issue rewrites none.
assert.deepEqual(row(d, 12).review.rounds.map((r) => [r.n, r.issue]), [[1, 1508], [2, 1508], [3, 1495]]);
d = review(again(d));
assert.deepEqual(row(d, 12).review.rounds.map((r) => r.issue), [1508, 1508, 1495, 1495]);
// A kept issue the row no longer lists refuses until --issue names one.
d = step(again(d), "set", { id: 12, field: "issues", value: [1508, 1600] }, "sage");
refused(() => review(d), /review issue #1495 is no longer one of its issues \(#1508, #1600\); name one with --issue/);
assert.equal(row(review(d, { issue: 1600 }), 12).review.issue, 1600);
assert.equal(kept(review(d, { issue: 1600 })), 1600);
// --issue belongs to the review request only.
refused(() => step(several, "move", mv(12, "blocked", { reason: "x", issue: 1508 }), "darkwing"), /--issue applies only to in-progress→in-review/);
});
test("the row schema refuses a review with a null issue", () => {
test("the row schema refuses a round with a null issue, and the A1 review shape (P2)", () => {
const r = structuredClone(row(review(row12Started([1508])), 12));
validateRow(r);
r.review.issue = null;
refused(() => validateRow(r), /review issue must be a positive integer/);
const bad = structuredClone(r);
bad.review.rounds[0].issue = null;
refused(() => validateRow(bad), /review round issue must be a positive integer/);
const noIssue = structuredClone(r);
delete noIssue.review.rounds[0].issue;
refused(() => validateRow(noIssue), /review round keys must be exactly n, op, by, at, issue, candidate, request/);
const a1 = structuredClone(r);
a1.review = { issue: 1508, rounds: a1.review.rounds };
refused(() => validateRow(a1), /review keys must be exactly rounds in that order/);
});
// R1: every state × target × actor class against 8.7's table, written from
@@ -392,7 +405,7 @@ test("field edits: who may change what", () => {
refused(() => step(d, "set", set(9, "closes", [1]), "sage", {}), /--reason|not one of/);
refused(() => step(d, "set", set(9, "closes", [1], "r"), "sage"), /not one of row 9's issues/);
const issues = step(narrowed, "set", set(9, "issues", [1508, 1510]), "sage");
assert.deepEqual(row(issues, 9).closes, [1508, 1510]);
assert.deepEqual(row(issues, 9).closes, []);
refused(() => step(d, "set", set(9, "piece", "Queue as data"), "sage"), /already/);
refused(() => step(d, "set", set(1, "piece", "x"), "jason"), /done rows never change/);
const repin = step(d, "set", set(9, "brief", "docs/plans/brief-b.md#Queue"), "sage", { brief: brief(undefined, undefined, BLOB2) });
@@ -401,6 +414,123 @@ test("field edits: who may change what", () => {
refused(() => step(d, "set", set(9, "brief", "docs/plans/brief-b.md#Queue"), "darkwing", { brief: brief() }), /privileged/);
});
test("set issues keeps a logged narrowing of closes (N10)", () => {
const set = (id, field, value, reason = null) => ({ id, field, value, reason });
const last = (d) => d.log.at(-1).result;
// Not narrowed: closes follows the issues.
const wide = step(genesisDoc(), "set", set(9, "issues", [1508, 1510]), "sage");
assert.deepEqual(row(wide, 9).closes, [1508, 1510]);
assert.deepEqual(last(wide).closes, { from: [1508], to: [1508, 1510], narrowed: false });
assert.match(last(wide).receipt, /row 9 issues: #1508→#1508, #1510; closes: #1508→#1508, #1510$/);
// Narrowed: closes keeps its intersection with the new issues.
const narrowed = step(wide, "set", set(9, "closes", [1510], "1508 closes elsewhere"), "sage");
const more = step(narrowed, "set", set(9, "issues", [1508, 1510, 1600]), "sage");
assert.deepEqual(row(more, 9).closes, [1510]);
assert.deepEqual(last(more).closes, { from: [1510], to: [1510], narrowed: true });
assert.match(last(more).receipt, /; closes: #1510→#1510 \(kept narrowed\)$/);
const fewer = step(more, "set", set(9, "issues", [1508, 1600]), "sage");
assert.deepEqual(row(fewer, 9).closes, []);
assert.match(last(fewer).receipt, /; closes: #1510→none \(kept narrowed\)$/);
refused(() => step(narrowed, "set", set(9, "issues", [1508, 1510]), "sage"), /row 9 issues is already/);
// The results replay, and other fields carry no closes.
loadDoc(Buffer.from(serialize(fewer)));
assert.equal("closes" in last(step(genesisDoc(), "set", set(11, "piece", "Renamed"), "sage")), false);
});
// GFM's cell split: a backslash escapes the next character, and a pipe not
// escaped separates cells. The outer pipes are dropped first.
function gfmCells(line) {
const inner = line.trim().replace(/^\|/, "");
const cells = [""];
for (let i = 0; i < inner.length; i++) {
if (inner[i] === "\\" && i + 1 < inner.length) { cells[cells.length - 1] += inner[i] + inner[i + 1]; i++; continue; }
if (inner[i] === "|") cells.push("");
else cells[cells.length - 1] += inner[i];
}
if (cells.at(-1).trim() === "") cells.pop();
return cells.length;
}
test("text the table shows refuses \\ and <, everywhere it enters (N8)", () => {
const d = genesisDoc();
const bad = ["a\\| done | x", "back\\slash", "<b>bold</b>", "a < b"];
const set = (field, value) => ({ id: 11, field, value, reason: null });
for (const text of bad) {
const re = /must not contain \\ or </;
refused(() => canonArgs("add", { piece: text, gate: "g", brief: "docs/plans/brief-b.md#Queue" }), re);
refused(() => canonArgs("add", { piece: "p", gate: text, brief: "docs/plans/brief-b.md#Queue" }), re);
refused(() => canonArgs("add", { piece: "p", gate: "g", note: text, brief: "docs/plans/brief-b.md#Queue" }), re);
refused(() => canonArgs("add", { piece: "p", gate: "g", brief: `docs/plans/brief-b.md#${text}` }), re);
refused(() => canonArgs("move", mv(9, "blocked", { reason: text })), re);
refused(() => canonArgs("note", { id: 9, text }), re);
refused(() => canonArgs("set", set("piece", text)), re);
refused(() => canonArgs("set", set("gate", text)), re);
refused(() => canonArgs("set", set("brief", `docs/plans/brief-b.md#${text}`)), re);
// A hand-built row or log entry is refused the same way.
const r = row(d, 9);
for (const [k, v] of [["piece", text], ["gate", text], ["note", text], ["brief", { ...r.brief, anchor: text }]]) {
refused(() => validateRow({ ...r, [k]: v }), re);
}
refused(() => validateRow({ ...r, state: "blocked", previousState: "briefed", blockedReason: text }), re);
refused(() => loadDoc(Buffer.from(serialize(genesisDoc(MAP_ROWS.map((m) => (m.id === 9 ? { ...m, piece: text } : m)))))), re);
}
});
test("every accepted text renders to nine cells on every row (N8)", () => {
const base = row(genesisDoc(), 9);
const chars = [];
for (let c = 0x20; c < 0x7f; c++) chars.push(String.fromCharCode(c));
chars.push("é", "§", "—", String.fromCharCode(0xa0), "\u{1f600}");
const texts = ["a\\| done | x", "|", "||", "`|`", "a | b", "x\\", "\\|\\|", "<!-- x -->", "&#124;"];
for (const c of chars) texts.push(`x${c}y`, `x${c}|${c}y`, `x${c}${c}|`);
const fields = [
(t) => ({ piece: t }), (t) => ({ gate: t }), (t) => ({ note: t }), (t) => ({ brief: { ...base.brief, anchor: t } }),
(t) => ({ state: "blocked", previousState: "briefed", blockedReason: t }),
];
const accepted = new Set();
for (const t of texts) {
for (const f of fields) {
const r = { ...base, ...f(t) };
try { validateRow(r); } catch (err) { if (err instanceof QueueError) continue; throw err; }
accepted.add(t);
const lines = render([r], 1).split("\n").filter((l) => l.startsWith("|"));
for (const l of lines) assert.equal(gfmCells(l), 9, `${JSON.stringify(t)} renders ${JSON.stringify(l)}`);
}
}
// Not vacuous: pipes and backticks pass; the two refused characters don't.
for (const t of ["|", "a | b", "`|`", "x|y"]) assert.ok(accepted.has(t), t);
for (const t of ["a\\| done | x", "x<y", "x\\y"]) assert.ok(!accepted.has(t), t);
});
test("replay holds every op id to the caller's rule (N11)", () => {
const load = (d) => loadDoc(Buffer.from(serialize(d)));
const d = genesisDoc();
// 72 characters replay; 73 to 80 no longer do.
load(step(d, "note", { id: 9, text: "x" }, "darkwing", {}, "a".repeat(72)));
for (const n of [73, 80]) {
refused(() => load(step(d, "note", { id: 9, text: "x" }, "darkwing", {}, "a".repeat(n))), /log entry 1 op .* must match/);
}
// .outcome is reserved on every verb, accept-history included.
refused(() => load(step(d, "accept-history", { reason: "r" }, "sage", { oldWitness: null }, "accept-history-1.outcome")), /log entry 1 op .* ends in \.outcome/);
refused(() => load(step(d, "note", { id: 9, text: "x" }, "darkwing", {}, "note-row9.outcome")), /\.outcome, which is reserved/);
// The genesis op is checked too.
load(genesisDoc(MAP_ROWS, "genesis-ok-0001"));
for (const op of ["short", "Genesis-op-0001", "genesis-op-0001.outcome", "g".repeat(73)]) {
refused(() => load(genesisDoc(MAP_ROWS, op)), /log entry 0 op/);
}
// The ops a row carries, a claim and a review round, follow the same rule.
const claimed = structuredClone(row(row9Started(), 9));
const reviewed = structuredClone(row(review(row12Started([1508])), 12));
validateRow(claimed);
validateRow(reviewed);
for (const op of ["a".repeat(73), "start-row9.outcome"]) {
refused(() => validateRow({ ...claimed, claim: { ...claimed.claim, op } }), /claim op/);
const r = structuredClone(reviewed);
r.review.rounds[0].op = op;
refused(() => validateRow(r), /review round op/);
}
});
test("note: owner, listed reviewer or privileged; empty clears", () => {
const d = genesisDoc();
const n = step(d, "note", { id: 9, text: "from the reviewer" }, "filbert");