// The pure layer: schema, serialization, the 8.7 matrix, replay, render, // view classification and `next` (8.8). No file or git access. import assert from "node:assert/strict"; import { test } from "node:test"; import { CALLER_OP_RE, LOG_OP_RE, STATES, applyEntry, buildDoc, canonArgs, classifyView, countHeading, genesisReceipt, genesisRows, gitBlobId, loadDoc, nextFor, parseManifest, parseMigrationMap, render, rowsArray, serialize, sha256, splitView, validateRow, } from "../src/queue.mjs"; import { QueueError } from "../src/errors.mjs"; import { MAP_ROWS, mapText } from "./helpers.mjs"; const ROOT = "/srv/repo"; const BLOB = "a".repeat(40); const BLOB2 = "b".repeat(40); let clock = 0; const at = () => new Date(Date.UTC(2026, 8, 26, 12, 0, clock++)).toISOString(); function brief(path = "docs/plans/brief-b.md", anchor = "Queue", blob = BLOB) { return { path, anchor, blob }; } // A genesis document built the way store.mjs builds one. function genesisDoc(rows = MAP_ROWS) { 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 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" }, 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) }, viewSha: sha256(body), }; return { version: 1, canonicalRoot: ROOT, revision: 0, rows: grows, log: [entry] }; } // Appends one op the way store.mjs does and returns the new document. function step(doc, verb, args, by, resolved = {}, op = `op-${verb}-${doc.revision + 1}-xxxx`) { const { state } = loadDoc(Buffer.from(serialize(doc))); const entry = { rev: doc.revision + 1, op, verb, args: canonArgs(verb, args), by, at: at(), semantics: 1, result: null, viewSha: null }; const out = applyEntry(state, entry, resolved); entry.result = out.result; entry.viewSha = sha256(render(rowsArray(out.state), entry.rev)); return buildDoc(doc.canonicalRoot, out.state, [...doc.log, entry]); } function refused(fn, re) { assert.throws(fn, (err) => err instanceof QueueError && err.code === 2 && re.test(err.message)); } const mv = (id, to, extra = {}) => ({ id, to, reason: null, candidate: null, evidence: null, issue: null, ...extra }); const row = (doc, id) => doc.rows.find((r) => r.id === id); const MANIFEST = `${"c".repeat(64)} packages/queue/src/queue.mjs\n`; const CAND = { kind: "manifest", digest: sha256(MANIFEST), text: MANIFEST }; // Row 9 in progress (row 6 blocked first, since row 9 waits on 6 settled). function row9Started() { let d = genesisDoc(); d = step(d, "move", mv(6, "blocked", { reason: "paused" }), "darkwing"); return step(d, "move", mv(9, "in-progress"), "darkwing"); } test("genesis document serializes deterministically and replays", () => { const doc = genesisDoc(); const text = serialize(doc); assert.equal(serialize(JSON.parse(text)), text); assert.ok(text.endsWith("}\n")); const loaded = loadDoc(Buffer.from(text)); assert.equal(loaded.branch, "refactor"); assert.deepEqual(rowsArray(loaded.state), doc.rows); assert.deepEqual(row(doc, 6).claim, { seat: "darkwing", op: "genesis-op-0001" }); assert.equal(row(doc, 9).claim, null); }); test("a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization", () => { const doc = step(genesisDoc(), "note", { id: 9, text: "hello" }, "darkwing"); const edited = JSON.parse(serialize(doc)); edited.rows.find((r) => r.id === 9).note = "changed by hand"; refused(() => loadDoc(Buffer.from(serialize(edited))), /do not equal the replay/); const loose = serialize(doc).replace('"revision": 1', '"revision": 1'); refused(() => loadDoc(Buffer.from(loose)), /re-serialize byte for byte/); const reordered = serialize(doc).replace(/"version": 1,\n "canonicalRoot": "[^"]*",/, `"canonicalRoot": "${ROOT}",\n "version": 1,`); refused(() => loadDoc(Buffer.from(reordered)), /keys must be exactly/); }); test("a tampered result, receipt or viewSha fails replay", () => { const doc = step(genesisDoc(), "note", { id: 9, text: "hello" }, "darkwing"); const a = JSON.parse(serialize(doc)); a.log[1].result.receipt = "ok forged"; refused(() => loadDoc(Buffer.from(serialize(a))), /result differs from its replay/); const b = JSON.parse(serialize(doc)); b.log[1].viewSha = "0".repeat(64); refused(() => loadDoc(Buffer.from(serialize(b))), /viewSha is not the render/); const c = JSON.parse(serialize(doc)); c.log[1].by = "rocko"; refused(() => loadDoc(Buffer.from(serialize(c))), /does not replay/); }); test("op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries", () => { assert.ok(CALLER_OP_RE.test("a".repeat(72))); assert.ok(!CALLER_OP_RE.test("a".repeat(73))); assert.ok(!CALLER_OP_RE.test("short")); assert.ok(!CALLER_OP_RE.test("-leading-dash")); assert.ok(LOG_OP_RE.test(`${"a".repeat(72)}.outcome`)); assert.ok(!LOG_OP_RE.test(`${"a".repeat(73)}.outcome`)); }); test("add: defaults for an ordinary seat, privileged extras, refusals", () => { const d = genesisDoc(); const add = { piece: "New", gate: "tests pass", brief: "docs/plans/brief-b.md#Queue", issues: [1508] }; const d1 = step(d, "add", add, "dewey", { brief: brief() }); const r = row(d1, 12); assert.equal(r.owner, "dewey"); assert.equal(r.state, "queued"); assert.deepEqual(r.closes, [1508]); assert.equal(r.gateOwner, "jason"); assert.deepEqual([r.after, r.reviewers, r.required, r.claim], [[], [], false, null]); assert.match(d1.log[1].result.receipt, /row 12 none→queued$/); refused(() => step(d, "add", { ...add, owner: "rocko" }, "dewey", { brief: brief() }), /only a privileged actor may set owner/); refused(() => step(d, "add", { ...add, reviewers: ["rocko"] }, "dewey", { brief: brief() }), /reviewers/); refused(() => step(d, "add", { ...add, required: true }, "dewey", { brief: brief() }), /required/); const d2 = step(d, "add", { ...add, owner: "rocko", gateOwner: "filbert", after: [{ id: 9, when: "done" }], reviewers: ["dewey"], required: true }, "sage", { brief: brief() }); const r2 = row(d2, 12); assert.deepEqual([r2.owner, r2.gateOwner, r2.required, r2.after], ["rocko", "filbert", true, [{ id: 9, when: "done" }]]); refused(() => step(d, "add", { ...add, after: [{ id: 99, when: "done" }] }, "sage", { brief: brief() }), /missing row 99/); // ids come from the high-water mark, so a retired id is never reused. const d3 = step(d2, "add", add, "dewey", { brief: brief() }); assert.ok(row(d3, 13)); }); test("matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied", () => { let d = step(genesisDoc(), "add", { piece: "N", gate: "g", brief: "docs/plans/brief-b.md#Queue" }, "dewey", { brief: brief() }); refused(() => step(d, "move", mv(12, "briefed"), "dewey"), /privileged/); d = step(d, "move", mv(12, "briefed"), "sage"); refused(() => step(d, "move", mv(12, "in-progress"), "sage"), /only the owner \(dewey\) may start/); d = step(d, "move", mv(12, "in-progress"), "dewey"); assert.deepEqual(row(d, 12).claim.seat, "dewey"); refused(() => step(d, "move", mv(12, "in-progress"), "dewey"), /not a transition/); // row 9 waits on row 6 settled; row 6 is in progress at genesis. refused(() => step(genesisDoc(), "move", mv(9, "in-progress"), "darkwing"), /waits on 6 \(settled; now in-progress\)/); const d9 = row9Started(); assert.equal(row(d9, 9).state, "in-progress"); }); test("matrix: release, review round, changes requested and waiting-on-jason", () => { let d = row9Started(); refused(() => step(d, "move", mv(9, "briefed"), "darkwing"), /use `queue release 9`/); refused(() => step(d, "release", { id: 9 }, "dewey"), /only the claimant/); const released = step(d, "release", { id: 9 }, "darkwing"); assert.deepEqual([row(released, 9).state, row(released, 9).claim], ["briefed", null]); step(d, "release", { id: 9 }, "sage"); refused(() => step(d, "move", mv(9, "in-review"), "darkwing"), /needs --candidate/); 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.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"); assert.equal(row(d, 9).claim.seat, "darkwing"); d = step(d, "move", mv(9, "in-review", { candidate: "y" }), "darkwing", { candidate: { kind: "commit", digest: BLOB2, text: null } }); assert.equal(row(d, 9).review.rounds.length, 2); const w = step(d, "move", mv(9, "waiting-on-jason"), "sage"); assert.equal(row(w, 9).claim.seat, "darkwing"); refused(() => step(w, "move", mv(9, "done"), "sage"), /sage closes a waiting-on-jason row only with --evidence/); refused(() => step(w, "move", mv(9, "done"), "darkwing"), /only jason/); const done = step(w, "move", mv(9, "done", { evidence: "Jason approved in thread X" }), "sage"); assert.deepEqual([row(done, 9).state, row(done, 9).claim], ["done", null]); const doneJ = step(w, "move", mv(9, "done"), "jason"); refused(() => step(doneJ, "move", mv(9, "blocked", { reason: "x" }), "jason"), /done rows never change/); refused(() => step(doneJ, "note", { id: 9, text: "x" }, "jason"), /notes are closed/); }); test("matrix J5: in-review→done by the gate owner with evidence naming the current round", () => { let d = row9Started(); d = step(d, "move", mv(9, "in-review", { candidate: "x" }), "darkwing", { candidate: CAND }); const ev = `comment=4242,round=1,candidate=${CAND.digest}`; refused(() => step(d, "move", mv(9, "done"), "filbert"), /--evidence comment=,round=,candidate=/); refused(() => step(d, "move", mv(9, "done", { evidence: `comment=4242,candidate=${CAND.digest}` }), "filbert"), /round=/); refused(() => step(d, "move", mv(9, "done", { evidence: `comment=1,round=1,candidate=${"d".repeat(64)}` }), "filbert"), /is not round 1's candidate/); refused(() => step(d, "move", mv(9, "done", { evidence: `comment=4242,round=2,candidate=${CAND.digest}` }), "filbert"), /evidence names round 2; row 9 is in round 1/); refused(() => step(d, "move", mv(9, "done", { evidence: ev }), "rocko"), /only the gate owner \(filbert\)/); const done = step(d, "move", mv(9, "done", { evidence: ev }), "filbert"); assert.equal(row(done, 9).state, "done"); step(d, "move", mv(9, "done", { evidence: ev }), "sage"); // Row 11's gate is Jason's: in-review→done is refused for everyone. let e = step(genesisDoc(), "move", mv(11, "in-progress"), "dewey"); e = step(e, "move", mv(11, "in-review", { candidate: "x" }), "dewey", { candidate: CAND }); refused(() => step(e, "move", mv(11, "done", { evidence: ev }), "jason"), /gate is Jason's/); // Changes requested, then the same candidate again: round 1's comment // does not close round 2 (8.7, R3). d = step(d, "move", mv(9, "in-progress"), "darkwing"); d = step(d, "move", mv(9, "in-review", { candidate: "x" }), "darkwing", { candidate: CAND }); assert.deepEqual(row(d, 9).review.rounds.map((r) => r.candidate.digest), [CAND.digest, CAND.digest]); refused(() => step(d, "move", mv(9, "done", { evidence: ev }), "filbert"), /evidence names round 1; row 9 is in round 2/); const done2 = step(d, "move", mv(9, "done", { evidence: `comment=4343,round=2,candidate=${CAND.digest}` }), "filbert"); assert.equal(done2.log.at(-1).result.round, 2); }); // A row 12 owned by darkwing with the given issues, started, so the next // move is the review request. function row12Started(issues, { gateOwner = "filbert" } = {}) { let d = step(genesisDoc(), "add", { piece: "N", gate: "g", brief: "docs/plans/brief-b.md#Queue", issues, owner: "darkwing", gateOwner }, "sage", { brief: brief() }); d = step(d, "move", mv(12, "briefed"), "sage"); return step(d, "move", mv(12, "in-progress"), "darkwing"); } 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"); 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.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\)/); // Several: --issue is required and must be one of the row's; the lowest // number is not a default. const several = row12Started([1495, 1508]); 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); // 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]); d = review(again(d), { issue: 1495 }); assert.deepEqual([row(d, 12).review.issue, row(d, 12).review.rounds.length], [1495, 3]); // 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); // --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", () => { const r = structuredClone(row(review(row12Started([1508])), 12)); validateRow(r); r.review.issue = null; refused(() => validateRow(r), /review issue must be a positive integer/); }); // R1: every state × target × actor class against 8.7's table, written from // the spec rather than from queue.mjs. Row 12 is owned by darkwing; the // gate owner is filbert, jason or the owner; rocko is any other seat. const ACTORS = ["darkwing", "filbert", "rocko", "sage", "jason"]; const PRIV = new Set(["sage", "jason"]); function specAllows({ from, prev, to, by, gateOwner, required }) { const own = by === "darkwing"; const priv = PRIV.has(by); if (from === "done") return false; if (to === "blocked") return ["queued", "briefed", "in-progress", "in-review", "waiting-on-jason"].includes(from) && (own || priv); if (from === "blocked") return to === prev && (own || priv); const edge = `${from}→${to}`; switch (edge) { case "queued→briefed": return priv; case "briefed→in-progress": return own; case "in-progress→in-review": return own; // the claimant is the owner case "in-review→in-progress": case "in-review→waiting-on-jason": return own || priv; case "waiting-on-jason→done": return by === "jason" || by === "sage"; // sage with evidence, which the case supplies case "in-review→done": return gateOwner !== "jason" && (by === gateOwner || priv); case "queued→parked": case "briefed→parked": return by === "jason" && !required; case "parked→queued": return by === "jason"; default: return false; // in-progress→briefed is `release`, not `move` } } function matrixStates(gateOwner, required) { let q = step(genesisDoc(), "add", { piece: "M", gate: "g", brief: "docs/plans/brief-b.md#Queue", issues: [1508], owner: "darkwing", gateOwner, required, }, "sage", { brief: brief() }); const out = [{ from: "queued", doc: q }]; if (!required) out.push({ from: "parked", doc: step(q, "move", mv(12, "parked"), "jason") }); const b = step(q, "move", mv(12, "briefed"), "sage"); const p = step(b, "move", mv(12, "in-progress"), "darkwing"); const r = review(p); const w = step(r, "move", mv(12, "waiting-on-jason"), "sage"); out.push({ from: "briefed", doc: b }, { from: "in-progress", doc: p }, { from: "in-review", doc: r }, { from: "waiting-on-jason", doc: w }); out.push({ from: "done", doc: step(w, "move", mv(12, "done"), "jason") }); for (const s of [...out]) { if (!["done", "parked"].includes(s.from)) out.push({ from: "blocked", prev: s.from, doc: step(s.doc, "move", mv(12, "blocked", { reason: "r" }), "sage") }); } return out; } function matrixArgs(from, to) { const extra = {}; if (to === "blocked") extra.reason = "r"; if (from === "in-progress" && to === "in-review") extra.candidate = "x"; if (to === "done" && from === "in-review") extra.evidence = `comment=1,round=1,candidate=${CAND.digest}`; if (to === "done" && from === "waiting-on-jason") extra.evidence = "Jason approved in thread X"; return mv(12, to, extra); } test("matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not", () => { let allowed = 0; let refusals = 0; for (const gateOwner of ["filbert", "jason", "darkwing"]) { for (const required of [false, true]) { for (const { from, prev = null, doc } of matrixStates(gateOwner, required)) { assert.equal(row(doc, 12).state, from); for (const to of STATES) { for (const by of ACTORS) { const c = { from, prev, to, by, gateOwner, required }; const label = JSON.stringify(c); let got; try { got = step(doc, "move", matrixArgs(from, to), by, { candidate: CAND }); } catch (err) { assert.ok(err instanceof QueueError && err.code === 2, `${label}: ${err.stack}`); assert.equal(specAllows(c), false, `${label} refused: ${err.message}`); refusals++; continue; } assert.equal(specAllows(c), true, `${label} was allowed`); const r = row(got, 12); assert.equal(r.state, to, label); if (to === "blocked") assert.equal(r.previousState, from, label); if (from === "blocked") assert.deepEqual([r.previousState, r.blockedReason], [null, null], label); allowed++; } } // release: the claimant or a privileged actor, from in-progress only. for (const by of ACTORS) { const ok = from === "in-progress" && (by === "darkwing" || PRIV.has(by)); const label = JSON.stringify({ release: from, by, gateOwner, required }); if (ok) assert.deepEqual([row(step(doc, "release", { id: 12 }, by), 12).state], ["briefed"], label); else assert.throws(() => step(doc, "release", { id: 12 }, by), (err) => err instanceof QueueError && err.code === 2, label); } } } } assert.ok(allowed > 100 && refusals > 1000, `allowed ${allowed}, refused ${refusals}`); }); test("matrix: blocked keeps the claim and returns only to previousState", () => { let d = row9Started(); refused(() => step(d, "move", mv(9, "blocked"), "darkwing"), /needs --reason/); refused(() => step(d, "move", mv(9, "blocked", { reason: "x" }), "rocko"), /claimed by darkwing/); d = step(d, "move", mv(9, "blocked", { reason: "waiting on git" }), "darkwing"); const r = row(d, 9); assert.deepEqual([r.state, r.previousState, r.blockedReason, r.claim.seat], ["blocked", "in-progress", "waiting on git", "darkwing"]); refused(() => step(d, "move", mv(9, "blocked", { reason: "y" }), "darkwing"), /already blocked; update the reason with note/); const noted = step(d, "note", { id: 9, text: "now waiting on review" }, "darkwing"); assert.equal(row(noted, 9).blockedReason, "now waiting on review"); refused(() => step(d, "note", { id: 9, text: "" }, "darkwing"), /reason cannot be empty/); refused(() => step(d, "move", mv(9, "briefed"), "darkwing"), /returns only there/); const back = step(d, "move", mv(9, "in-progress"), "sage"); assert.deepEqual([row(back, 9).state, row(back, 9).previousState, row(back, 9).blockedReason, row(back, 9).claim.seat], ["in-progress", null, null, "darkwing"]); refused(() => step(genesisDoc(), "move", mv(1, "blocked", { reason: "x" }), "sage"), /done rows never change/); refused(() => step(genesisDoc(), "move", mv(8, "blocked", { reason: "x" }), "jason"), /not a transition/); }); test("matrix J4: parking is Jason's, refused while required; unpark returns to queued", () => { const d = genesisDoc(); refused(() => step(d, "move", mv(11, "parked"), "sage"), /only jason may park/); refused(() => step(d, "move", mv(9, "parked"), "jason"), /required and cannot be parked/); const p = step(d, "move", mv(11, "parked"), "jason"); refused(() => step(p, "move", mv(11, "briefed"), "jason"), /not a transition/); refused(() => step(p, "move", mv(11, "queued"), "sage"), /only jason may unpark/); const u = step(p, "move", mv(11, "queued"), "jason"); assert.equal(row(u, 11).state, "queued"); refused(() => step(p, "set", { id: 11, field: "required", value: true }, "sage"), /parked; unpark it/); refused(() => step(p, "note", { id: 11, text: "x" }, "jason"), /notes are closed/); refused(() => step(row9Started(), "move", mv(9, "parked"), "jason"), /not a transition|required/); }); test("field edits: who may change what", () => { const d = genesisDoc(); const set = (id, field, value, reason = null) => ({ id, field, value, reason }); refused(() => step(d, "set", set(11, "piece", "x"), "dewey"), /privileged/); assert.match(step(d, "set", set(11, "piece", "Renamed"), "sage").log.at(-1).result.receipt, /row 11 piece: Brief template→Renamed$/); refused(() => step(d, "set", set(9, "after", []), "sage"), /only jason may change after on a required row/); step(d, "set", set(9, "after", []), "jason"); step(d, "set", set(11, "after", [{ id: 9, when: "done" }]), "sage"); refused(() => step(d, "set", set(11, "after", [{ id: 11, when: "done" }]), "sage"), /lists itself/); refused(() => step(d, "set", set(9, "required", false), "sage"), /only jason may clear required/); const req = step(d, "set", set(11, "required", true), "sage"); assert.equal(row(req, 11).requiredSince, req.log.at(-1).at); const cleared = step(req, "set", set(11, "required", false), "jason"); assert.equal(row(cleared, 11).requiredSince, null); refused(() => step(d, "set", set(9, "closes", []), "sage"), /needs --reason/); const narrowed = step(d, "set", set(9, "closes", [], "closed elsewhere"), "sage"); assert.deepEqual(row(narrowed, 9).closes, []); 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]); 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) }); assert.equal(row(repin, 9).brief.blob, BLOB2); refused(() => step(d, "set", set(9, "brief", "docs/plans/brief-b.md#Queue"), "sage", { brief: brief() }), /already pinned/); refused(() => step(d, "set", set(9, "brief", "docs/plans/brief-b.md#Queue"), "darkwing", { brief: brief() }), /privileged/); }); test("note: owner, listed reviewer or privileged; empty clears", () => { const d = genesisDoc(); const n = step(d, "note", { id: 9, text: "from the reviewer" }, "filbert"); assert.equal(row(n, 9).note, "from the reviewer"); refused(() => step(d, "note", { id: 9, text: "x" }, "rocko"), /only the owner/); assert.equal(row(step(n, "note", { id: 9, text: "" }, "darkwing"), 9).note, null); refused(() => step(d, "note", { id: 9, text: "two\nlines" }, "darkwing"), /one line/); refused(() => step(d, "note", { id: 9, text: `sep${String.fromCharCode(0x2028)}x` }, "darkwing"), /one line/); }); test("assign moves the claim with the owner; done clears it", () => { let d = row9Started(); refused(() => step(d, "assign", { id: 9, seat: "dewey" }, "darkwing"), /privileged/); d = step(d, "assign", { id: 9, seat: "dewey" }, "sage", {}, "assign-row-9-dewey"); assert.deepEqual(row(d, 9).claim, { seat: "dewey", op: "assign-row-9-dewey" }); assert.match(d.log.at(-1).result.receipt, /owner: darkwing→dewey$/); refused(() => step(d, "move", mv(9, "in-review", { candidate: "x" }), "darkwing", { candidate: CAND }), /only the claimant \(dewey\)/); const unclaimed = step(genesisDoc(), "assign", { id: 11, seat: "rocko" }, "sage"); assert.equal(row(unclaimed, 11).claim, null); }); test("render is byte-stable and escapes pipes", () => { const d = step(genesisDoc(), "set", { id: 11, field: "piece", value: "a | b", reason: null }, "sage"); const a = render(d.rows, d.revision); assert.equal(render(JSON.parse(JSON.stringify(d.rows)).reverse(), d.revision), a); assert.match(a, /\| 11 \| a \\\| b \|/); assert.match(a, /revision 1 by/); assert.notEqual(sha256(render(d.rows, 2)), sha256(a)); }); test("view classification: current, genuine stale, edited stale marker, changed current body, markers", () => { const d0 = genesisDoc(); const d1 = step(d0, "note", { id: 9, text: "one" }, "darkwing"); const wrap = (body) => `# Q\n\n\n${body}\ntail\n`; const cur = wrap(render(d1.rows, 1)); assert.equal(classifyView(cur, d1.log).state, "current"); const stale = classifyView(wrap(render(d0.rows, 0)), d1.log); assert.equal(stale.state, "stale"); assert.deepEqual(stale.unshown.map((e) => e.rev), [1]); assert.equal(classifyView(wrap(render(d0.rows, 0).replace("Row six", "Row 6")), d1.log).state, "unknown"); assert.equal(classifyView(cur.replace("| one |", "| two |"), d1.log).state, "unknown"); assert.equal(classifyView(cur.replace("\n", ""), d1.log).state, "unknown"); assert.equal(classifyView(`${cur}\n`, d1.log).state, "unknown"); assert.equal(splitView("\n\n"), null); assert.equal(classifyView(null, d1.log).state, "unknown"); }); test("next: resume, then review, then start, then wait, then nothing; lowest id first", () => { let d = genesisDoc(); const add = (owner, extra = {}) => ({ piece: `p-${owner}`, gate: "g", brief: "docs/plans/brief-b.md#Queue", owner, ...extra }); d = step(d, "add", add("rocko"), "sage", { brief: brief() }); // 12 d = step(d, "add", add("rocko", { reviewers: ["darkwing"], issues: [1508] }), "sage", { brief: brief() }); // 13 d = step(d, "add", add("darkwing"), "sage", { brief: brief() }); // 14 for (const id of [12, 13, 14]) d = step(d, "move", mv(id, "briefed"), "sage"); const rows = () => loadDoc(Buffer.from(serialize(d))).state.rows; const all = () => true; // darkwing resumes row 6 (claimed at genesis). assert.deepEqual([nextFor(rows(), "darkwing", all).action, nextFor(rows(), "darkwing", all).row.id], ["resume", 6]); d = step(d, "move", mv(13, "in-progress"), "rocko"); d = step(d, "move", mv(13, "in-review", { candidate: "x" }), "rocko", { candidate: CAND }); assert.equal(nextFor(rows(), "darkwing", all).row.id, 6); d = step(d, "move", mv(6, "blocked", { reason: "r" }), "darkwing"); const r = nextFor(rows(), "darkwing", all); assert.deepEqual([r.action, r.row.id], ["review", 13]); // rocko: row 12 briefed, row 13 in review as author: start before wait. assert.deepEqual([nextFor(rows(), "rocko", all).action, nextFor(rows(), "rocko", all).row.id], ["start", 12]); const flagged = nextFor(rows(), "rocko", () => false); assert.equal(flagged.briefDiffers, true); d = step(d, "move", mv(12, "parked"), "jason"); assert.deepEqual([nextFor(rows(), "rocko", all).action, nextFor(rows(), "rocko", all).row.id], ["wait", 13]); assert.equal(nextFor(rows(), "researcher", all).action, "nothing"); // row 9 (darkwing) waits on 6 settled, which blocked satisfies: start comes after review. d = step(d, "move", mv(13, "in-progress"), "rocko"); assert.deepEqual([nextFor(rows(), "darkwing", all).action, nextFor(rows(), "darkwing", all).row.id], ["start", 9]); }); test("canonical args make a retry's identity independent of list order", () => { const a = canonArgs("add", { piece: "p", gate: "g", brief: "a.md#X", issues: [3, 1], reviewers: ["rocko", "dewey"] }); assert.deepEqual([a.issues, a.reviewers, a.after, a.required], [[1, 3], ["dewey", "rocko"], null, null]); refused(() => canonArgs("add", { piece: "p", gate: "g", brief: "a.md#X", issues: [1, 1] }), /repeated/); refused(() => canonArgs("add", { piece: "p", gate: "g", brief: "a.md" }), /PATH#ANCHOR/); refused(() => canonArgs("add", { piece: "p", gate: "g", brief: "../x.md#A" }), /repo-relative/); refused(() => canonArgs("move", { id: 1, to: "finished" }), /unknown state/); refused(() => canonArgs("set", { id: 1, field: "owner", value: "x" }), /set cannot change/); }); test("manifests, headings and blob ids", () => { assert.equal(parseManifest(MANIFEST), 1); refused(() => parseManifest(`${"c".repeat(64)} one-space\n`), /not " "/); refused(() => parseManifest(`${"c".repeat(64)} a\n${"d".repeat(64)} a\n`), /twice/); refused(() => parseManifest("no newline"), /end with a newline/); const text = "# T\n\n## A\n\n```\n## A\n```\n\n### A \n"; assert.equal(countHeading(text, "A"), 2); assert.equal(countHeading("~~~\n## A\n~~~\n## A\n", "A"), 1); // git hash-object of "hello\n" assert.equal(gitBlobId(Buffer.from("hello\n")), "ce013625030ba8dba906f756967f9e9ca394464a"); }); test("the migration map: one queue-map block, exact keys", () => { assert.equal(parseMigrationMap(mapText()).rows.length, MAP_ROWS.length); refused(() => parseMigrationMap(mapText() + mapText()), /exactly one/); refused(() => parseMigrationMap("no block\n"), /exactly one/); const bad = MAP_ROWS.map((r) => ({ ...r })); delete bad[0].note; refused(() => parseMigrationMap(mapText(bad)), /keys must be exactly/); const noBrief = MAP_ROWS.map((r) => (r.id === 9 ? { ...r, brief: null } : r)); const map = parseMigrationMap(mapText(noBrief)); refused(() => loadDoc(Buffer.from(serialize(genesisDoc(map.rows)))), /only a done row may lack one/); });