Files
stack/packages/queue/tests/store.test.mjs
T
jason.woltjeandClaude Opus 5.5 6ca116b7ba 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]>
2026-09-26 20:14:09 -05:00

374 lines
25 KiB
JavaScript

// The CLI against scratch repositories: genesis, the canonical checks (8.3),
// op ids (8.6), claims, the view (8.5 outcomes), brief checks (8.13),
// verify, render and snapshot.
import assert from "node:assert/strict";
import { mkdirSync, readFileSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { test } from "node:test";
import { LEGACY, MAP_ROWS, PKG_SRC, cli, genesisCommitted, mapText, queueMd, scratchRepo, sh } from "./helpers.mjs";
const MAP = "agents/sage/work/queue-migration-map.md";
function ok(r, re) {
assert.equal(r.code, 0, `exit ${r.code}: ${r.err}`);
if (re) assert.match(r.out, re);
return r;
}
function no(r, code, re) {
assert.equal(r.code, code, `expected exit ${code}, got ${r.code}: ${r.out}${r.err}`);
if (re) assert.match(r.err, re);
return r;
}
const genesisArgs = (repo, over = {}) => [
"genesis", "--op", over.op ?? "genesis-2026-09-26", "--root", over.root ?? repo.root, "--branch", over.branch ?? "refactor", "--map", over.map ?? MAP,
];
function ready(t, o = {}) {
const repo = scratchRepo(t, o);
genesisCommitted(repo);
return repo;
}
const doc = (repo) => JSON.parse(readFileSync(repo.queuePath, "utf8"));
const row = (repo, id) => doc(repo).rows.find((r) => r.id === id);
test("genesis: refusals before anything is written", (t) => {
const repo = scratchRepo(t);
no(cli(repo, ["list"]), 2, /no docs\/plans\/queue.json; genesis has not run/);
no(cli(repo, genesisArgs(repo), { by: "darkwing" }), 2, /only a privileged actor/);
no(cli(repo, genesisArgs(repo)), 2, /no actor: pass --by NAME or set MOSAIC_AGENT_NAME/);
no(cli(repo, genesisArgs(repo, { root: repo.base }), { by: "sage" }), 2, /is not this checkout's toplevel/);
no(cli(repo, genesisArgs(repo, { branch: "main" }), { by: "sage" }), 2, /HEAD is refs\/heads\/refactor; the queue runs only on branch main/);
no(cli(repo, genesisArgs(repo, { map: "agents/sage/work/nope.md" }), { by: "sage" }), 2, /not a committed file in HEAD/);
no(cli(repo, genesisArgs(repo, { op: "short" }), { by: "sage" }), 2, /must match/);
const missingOp = genesisArgs(repo).filter((a, i, all) => a !== "--op" && all[i - 1] !== "--op");
no(cli(repo, missingOp, { by: "sage" }), 4, /--op ID is required/);
assert.throws(() => readFileSync(repo.queuePath));
});
test("genesis: the map must be committed, well formed, with committed briefs and seats", (t) => {
const uncommitted = scratchRepo(t, { commitMap: false });
no(cli(uncommitted, genesisArgs(uncommitted), { by: "sage" }), 2, /not a committed file in HEAD/);
const twice = scratchRepo(t, { map: mapText() + mapText() });
no(cli(twice, genesisArgs(twice), { by: "sage" }), 2, /exactly one ```json queue-map block/);
const anchor = scratchRepo(t, { map: mapText(MAP_ROWS.map((r) => (r.id === 11 ? { ...r, brief: { path: "docs/plans/brief-b.md", anchor: "Nope" } } : r))) });
no(cli(anchor, genesisArgs(anchor), { by: "sage" }), 2, /has 0 headings "Nope" in HEAD/);
const staged = scratchRepo(t, { map: mapText(MAP_ROWS.map((r) => (r.id === 11 ? { ...r, brief: { path: "docs/plans/new.md", anchor: "New" } } : r))) });
writeFileSync(join(staged.root, "docs/plans/new.md"), "## New\n");
staged.g("add", "docs/plans/new.md");
no(cli(staged, genesisArgs(staged), { by: "sage" }), 2, /new.md is not committed in HEAD; commit it first \(a staged brief is refused\)/);
const ghost = scratchRepo(t, { map: mapText(MAP_ROWS.map((r) => (r.id === 11 ? { ...r, owner: "ghost" } : r))) });
no(cli(ghost, genesisArgs(ghost), { by: "sage" }), 2, /row 11 owner ghost is not jason, coordinator, unassigned or a seat/);
const noBrief = scratchRepo(t, { map: mapText(MAP_ROWS.map((r) => (r.id === 11 ? { ...r, brief: null } : r))) });
no(cli(noBrief, genesisArgs(noBrief), { by: "sage" }), 2, /only a done row may lack one/);
});
test("genesis: markers, a stray witness, once only; a retry returns the receipt", (t) => {
const repo = scratchRepo(t);
writeFileSync(repo.viewPath, "# QUEUE\n\nno markers\n");
no(cli(repo, genesisArgs(repo), { by: "sage" }), 2, /needs the two queue markers/);
writeFileSync(repo.viewPath, queueMd());
writeFileSync(join(repo.gitDir, "mosaic-queue.head"), "{}\n");
no(cli(repo, genesisArgs(repo), { by: "sage" }), 2, /the witness .* exists without docs\/plans\/queue.json/);
unlinkSync(join(repo.gitDir, "mosaic-queue.head"));
const g = ok(cli(repo, genesisArgs(repo), { by: "sage" }), /^ok genesis-2026-09-26 rev 0 genesis 5 rows$/m);
const d = doc(repo);
assert.equal(d.canonicalRoot, repo.root);
assert.equal(d.log[0].result.legacyView, LEGACY);
assert.equal(d.log[0].result.mapBlob, repo.g("rev-parse", `HEAD:${MAP}`).trim());
assert.match(readFileSync(repo.viewPath, "utf8"), /^# QUEUE\n\nHeader prose\.\n\n<!-- mosaic-queue:begin -->\n\nGenerated from `docs\/plans\/queue.json` revision 0/);
assert.match(readFileSync(repo.viewPath, "utf8"), /<!-- mosaic-queue:end -->\n\nParked entries below\.\n$/);
ok(cli(repo, genesisArgs(repo), { by: "sage" }), new RegExp(`^${g.out.trim()} \\(already recorded at rev 0\\)`));
no(cli(repo, genesisArgs(repo, { op: "genesis-other-01" }), { by: "sage" }), 2, /exists; genesis runs once/);
// Before the genesis commit, reads work and ops refuse.
ok(cli(repo, ["list"]), /^1\tdone\tdarkwing\tFinished thing\n6\tin-progress/);
no(cli(repo, ["note", "9", "x", "--op", "note-row9-001"], { by: "darkwing" }), 2, /genesis not committed: HEAD has no docs\/plans\/queue.json/);
repo.g("add", "docs/plans/queue.json", "docs/plans/QUEUE.md");
repo.g("commit", "-q", "-m", "genesis");
rmSync(repo.queuePath);
no(cli(repo, genesisArgs(repo, { op: "genesis-again-1" }), { by: "sage" }), 2, /HEAD already has docs\/plans\/queue.json|witness .* exists/);
});
test("genesis: a file holding genesis alone with no witness is confirmed by sync or a retry", (t) => {
const repo = ready(t);
unlinkSync(join(repo.gitDir, "mosaic-queue.head"));
no(cli(repo, ["list"]), 2, /witness .* is missing; .*run `scripts\/mosaic queue sync` \(the file holds genesis alone\)/);
ok(cli(repo, genesisArgs(repo), { by: "sage" }), /already recorded at rev 0/);
unlinkSync(join(repo.gitDir, "mosaic-queue.head"));
ok(cli(repo, ["sync"]), /durable now, never acknowledged: genesis-2026-09-26 by sage/);
ok(cli(repo, ["sync"]), /nothing to confirm: rev 0 is durable and witnessed/);
});
test("canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works", (t) => {
const repo = ready(t);
ok(cli(repo, ["verify"]), /^ok verify rev 0/);
const wt = join(repo.base, "wt");
repo.g("worktree", "add", "-q", "--detach", wt);
no(cli(repo, ["list"], { cwd: wt }), 2, /is a linked worktree; the queue runs only in the canonical checkout/);
const clone = join(repo.base, "clone");
sh("git", ["clone", "-q", "--branch", "refactor", repo.root, clone], { env: repo.env });
no(cli(repo, ["list"], { cwd: clone }), 2, new RegExp(`this checkout is ${clone}; the queue's canonical root is ${repo.root}`));
const cloneCli = { ...repo, root: clone };
no(cli(cloneCli, ["list"], { cwd: clone }), 2, /the queue's canonical root is/);
repo.g("checkout", "-q", "--detach");
no(cli(repo, ["list"]), 2, /HEAD is detached; the queue runs only on branch refactor/);
repo.g("checkout", "-q", "-b", "other");
no(cli(repo, ["list"]), 2, /HEAD is refs\/heads\/other; the queue runs only on branch refactor/);
repo.g("checkout", "-q", "refactor");
no(cli(repo, ["list"], { env: { GIT_DIR: repo.gitDir } }), 2, /GIT_DIR is set/);
no(cli(repo, ["list"], { env: { GIT_WORK_TREE: repo.root } }), 2, /GIT_WORK_TREE is set/);
no(cli(repo, ["list"], { cwd: repo.base }), 2, /is not inside a git checkout/);
const foreign = sh(process.execPath, [join(PKG_SRC, "cli.mjs"), "list"], { cwd: repo.root, env: repo.env, allowFail: true });
assert.equal(foreign.status, 2);
assert.match(foreign.stderr, /this queue code .* is not under the canonical root/);
const link = join(repo.base, "link");
symlinkSync(repo.root, link);
ok(cli(repo, ["list"], { cwd: join(link, "docs") }), /^1\tdone/);
});
test("--by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12)", (t) => {
const warn = (by, env) => `warning: --by ${JSON.stringify(by)} differs from MOSAIC_AGENT_NAME=${JSON.stringify(env)}`;
// Genesis goes through the same check.
const fresh = scratchRepo(t);
const g = ok(cli(fresh, [...genesisArgs(fresh), "--by", "sage"], { by: "darkwing" }), /^ok genesis-2026-09-26 rev 0 genesis/);
assert.equal(g.err, `${warn("sage", "darkwing")}\n`);
const repo = ready(t);
// --by wins and the log records only the actor.
const r = ok(cli(repo, ["note", "9", "hello", "--op", "note-n12-0001", "--by", "darkwing"], { by: "dewey" }), /^ok note-n12-0001 rev 1/);
assert.equal(r.err.split("\n")[0], warn("darkwing", "dewey"));
const e = doc(repo).log.at(-1);
assert.deepEqual([e.by, Object.keys(e).join()], ["darkwing", "rev,op,verb,args,by,at,semantics,result,viewSha"]);
// A refusal carries it too.
const n = no(cli(repo, ["note", "9", "again", "--op", "note-n12-0002", "--by", "rocko"], { by: "darkwing" }), 2, /only the owner/);
assert.equal(n.err.trimEnd().split("\n").at(-1), warn("rocko", "darkwing"));
// Same name, or only one of the two: no warning.
assert.equal(ok(cli(repo, ["note", "9", "three", "--op", "note-n12-0003", "--by", "darkwing"], { by: "darkwing" })).err, "");
assert.equal(ok(cli(repo, ["note", "9", "four", "--op", "note-n12-0004"], { by: "darkwing" })).err, "");
assert.equal(ok(cli(repo, ["note", "9", "five", "--op", "note-n12-0005", "--by", "darkwing"])).err, "");
assert.equal(ok(cli(repo, ["note", "9", "six", "--op", "note-n12-0006", "--by", "darkwing"], { by: "" })).err, "");
});
test("op ids: missing, too long, reserved; a retry answers; another payload refuses", (t) => {
const repo = ready(t);
no(cli(repo, ["note", "9", "hello"], { by: "darkwing" }), 4, /--op ID is required/);
no(cli(repo, ["note", "9", "hello", "--op", "note-by-ghost"], { by: "ghost" }), 2, /actor ghost is not jason or a seat under agents/);
no(cli(repo, ["note", "9", "hello", "--op", "note-by-nobody"]), 2, /no actor/);
no(cli(repo, ["note", "9", "hello", "--op", "a".repeat(73)], { by: "darkwing" }), 2, /8 to 72 characters/);
ok(cli(repo, ["note", "9", "hello", "--op", "a".repeat(72)], { by: "darkwing" }));
no(cli(repo, ["note", "9", "hello", "--op", "note-row9.outcome"], { by: "darkwing" }), 2, /reserved/);
const first = ok(cli(repo, ["note", "9", "hi", "--op", "note-row9-0001"], { by: "darkwing" }), /^ok note-row9-0001 rev 2 row 9 note$/m);
ok(cli(repo, ["note", "9", "hi", "--op", "note-row9-0001"], { by: "darkwing" }), new RegExp(`^${first.out.trim()} \\(already recorded at rev 2\\)$`, "m"));
no(cli(repo, ["note", "9", "other", "--op", "note-row9-0001"], { by: "darkwing" }), 2, /recorded at rev 2 as note with other arguments; a new operation needs a new op id/);
no(cli(repo, ["release", "9", "--op", "note-row9-0001"], { by: "darkwing" }), 2, /as note with other arguments/);
no(cli(repo, ["move", "6", "in-progress", "--op", "move-row6-again"], { by: "darkwing" }), 2, /not a transition/);
assert.equal(doc(repo).revision, 2);
});
test("a retried add returns the id it first allocated, after reassignment and after done", (t) => {
const repo = ready(t);
const add = ["add", "--op", "add-new-row-01", "--piece", "New thing", "--gate", "Jason says", "--brief", "docs/plans/brief-b.md#Template", "--owner", "rocko", "--issue", "#1508"];
const first = ok(cli(repo, add, { by: "sage" }), /row 12 none→queued$/m).out.trim();
ok(cli(repo, ["assign", "12", "dewey", "--op", "assign-12-dewey"], { by: "sage" }), /owner: rocko→dewey/);
ok(cli(repo, add, { by: "sage" }), new RegExp(`^${first} \\(already recorded at rev 1\\)$`, "m"));
ok(cli(repo, ["move", "12", "briefed", "--op", "brief-12-0001"], { by: "sage" }));
ok(cli(repo, ["move", "12", "in-progress", "--op", "start-12-0001"], { by: "dewey" }));
ok(cli(repo, ["move", "12", "in-review", "--candidate", "HEAD", "--op", "review-12-0001"], { by: "dewey" }), /in-progress→in-review round 1/);
ok(cli(repo, ["move", "12", "waiting-on-jason", "--op", "wait-12-00001"], { by: "sage" }));
ok(cli(repo, ["move", "12", "done", "--op", "done-12-00001"], { by: "jason" }));
ok(cli(repo, add, { by: "sage" }), new RegExp(`^${first} \\(already recorded at rev 1\\)$`, "m"));
assert.equal(doc(repo).rows.filter((r) => r.piece === "New thing").length, 1);
});
test("Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round", (t) => {
const repo = ready(t);
ok(cli(repo, ["move", "6", "blocked", "--reason", "paused", "--op", "block-6-00001"], { by: "darkwing" }));
ok(cli(repo, ["move", "9", "in-progress", "--op", "start-9-00001"], { by: "darkwing" }));
const review = ["move", "9", "in-review", "--candidate", "HEAD", "--op", "review-9-00001"];
cli(repo, review, { by: "darkwing" }); // result lost
ok(cli(repo, ["note", "9", "looking now", "--op", "note-9-000001"], { by: "filbert" }));
ok(cli(repo, review, { by: "darkwing" }), /round 1 on #1508 \(already recorded at rev 3\)/);
assert.equal(row(repo, 9).review.rounds.length, 1);
});
test("the review issue and the evidence round through the CLI (lead decision 23, 8.7)", (t) => {
const repo = ready(t);
ok(cli(repo, ["move", "6", "blocked", "--reason", "paused", "--op", "block-6-00001"], { by: "darkwing" }));
ok(cli(repo, ["set", "9", "issues", "1495,1508", "--op", "issues-9-0001"], { by: "sage" }));
ok(cli(repo, ["move", "9", "in-progress", "--op", "start-9-00001"], { by: "darkwing" }));
const review = (op, ...extra) => ["move", "9", "in-review", "--candidate", "HEAD", "--op", op, ...extra];
no(cli(repo, review("review-9-00001"), { by: "darkwing" }), 2, /lists several issues \(#1495, #1508\); name the review's issue with --issue/);
no(cli(repo, review("review-9-00001", "--issue", "1495", "--issue", "1508"), { by: "darkwing" }), 4, /one --issue/);
no(cli(repo, review("review-9-00001", "--issue", "#1600"), { by: "darkwing" }), 2, /--issue #1600 is not one of row 9's issues/);
ok(cli(repo, review("review-9-00001", "--issue", "#1508"), { by: "darkwing" }), /in-progress→in-review round 1 on #1508$/m);
const head = repo.g("rev-parse", "HEAD").trim();
no(cli(repo, ["move", "9", "done", "--evidence", `comment=7,candidate=${head}`, "--op", "done-9-000001"], { by: "filbert" }), 2, /round=<n>/);
ok(cli(repo, ["move", "9", "in-progress", "--op", "changes-9-0001"], { by: "darkwing" }));
ok(cli(repo, review("review-9-00002"), { by: "darkwing" }), /round 2 on #1508$/m);
no(cli(repo, ["move", "9", "done", "--evidence", `comment=7,round=1,candidate=${head}`, "--op", "done-9-000001"], { by: "filbert" }), 2, /evidence names round 1; row 9 is in round 2/);
ok(cli(repo, ["move", "9", "done", "--evidence", `comment=8,round=2,candidate=${head}`, "--op", "done-9-000002"], { by: "filbert" }), /in-review→done round 2$/m);
});
test("claims and add defaults through the CLI; candidates are manifests or reachable commits", (t) => {
const repo = ready(t);
ok(cli(repo, ["add", "--op", "add-by-dewey-1", "--piece", "Mine", "--gate", "tests", "--brief", "docs/plans/brief-b.md#Template"], { by: "dewey" }));
const r = row(repo, 12);
assert.deepEqual([r.owner, r.gateOwner, r.state, r.claim, r.brief.blob], ["dewey", "jason", "queued", null, repo.g("rev-parse", "HEAD:docs/plans/brief-b.md").trim()]);
no(cli(repo, ["add", "--op", "add-by-dewey-2", "--piece", "x", "--gate", "g", "--brief", "docs/plans/brief-b.md#Template", "--reviewer", "ghost"], { by: "sage" }), 2, /reviewer ghost is not jason or a seat/);
no(cli(repo, ["add", "--op", "add-by-dewey-3", "--piece", "x", "--gate", "g", "--brief", "docs/plans/brief-b.md"], { by: "dewey" }), 2, /PATH#ANCHOR/);
no(cli(repo, ["add", "--op", "add-by-dewey-4", "--piece", "x", "--gate", "g", "--brief", "docs/plans/brief-b.md#Queue", "--issue", "x"], { by: "dewey" }), 4);
ok(cli(repo, ["move", "11", "in-progress", "--op", "start-11-0001"], { by: "dewey" }));
assert.deepEqual(row(repo, 11).claim, { seat: "dewey", op: "start-11-0001" });
no(cli(repo, ["move", "11", "in-review", "--candidate", "no-such-thing", "--op", "review-11-001"], { by: "dewey" }), 2, /neither a manifest file nor a commit/);
const dangling = repo.g("commit-tree", "-m", "x", `${repo.g("rev-parse", "HEAD^{tree}").trim()}`).trim();
no(cli(repo, ["move", "11", "in-review", "--candidate", dangling, "--op", "review-11-002"], { by: "dewey" }), 2, /not reachable from any local branch or tag/);
const manifest = join(repo.base, "cand.sha256");
writeFileSync(manifest, `${"e".repeat(64)} packages/x.mjs\n`);
ok(cli(repo, ["move", "11", "in-review", "--candidate", manifest, "--op", "review-11-003"], { by: "dewey" }));
assert.equal(row(repo, 11).review.rounds[0].candidate.kind, "manifest");
ok(cli(repo, ["release", "6", "--op", "release-6-001"], { by: "sage" }), /in-progress→briefed/);
assert.equal(row(repo, 6).claim, null);
});
test("the working-brief check: a changed working copy refuses the start and flags next", (t) => {
const repo = ready(t);
writeFileSync(join(repo.root, "docs/plans/brief-b.md"), "# Briefs B\n\n## Queue\n\nEdited.\n\n## Template\n\nEdited.\n");
ok(cli(repo, ["next", "dewey"]), /^start row 11: Brief template; brief docs\/plans\/brief-b.md § Template; brief differs from pinned blob; ask the lead to re-pin$/m);
no(cli(repo, ["move", "11", "in-progress", "--op", "start-11-0001"], { by: "dewey" }), 2, /row 11: brief differs from pinned blob; ask the lead to re-pin/);
repo.g("commit", "-q", "-am", "edit brief");
no(cli(repo, ["move", "11", "in-progress", "--op", "start-11-0001"], { by: "dewey" }), 2, /brief differs from pinned blob/);
no(cli(repo, ["verify", "--current"]), 2, /brief drift against HEAD:\nrow 9: brief docs\/plans\/brief-b.md changed in HEAD .*\nrow 11:/);
ok(cli(repo, ["verify"]));
ok(cli(repo, ["set", "11", "brief", "docs/plans/brief-b.md#Template", "--op", "repin-11-0001"], { by: "sage" }), /row 11 brief: docs\/plans\/brief-b.md § Template @\w{12}→docs\/plans\/brief-b.md § Template @\w{12}/);
ok(cli(repo, ["next", "dewey"]), /^start row 11: Brief template; brief docs\/plans\/brief-b.md § Template$/m);
ok(cli(repo, ["move", "11", "in-progress", "--op", "start-11-0001"], { by: "dewey" }));
});
test("next: resume first, then nothing for an idle seat; needs a seat", (t) => {
const repo = ready(t);
ok(cli(repo, ["next", "darkwing"]), /^resume row 6: Row six/m);
ok(cli(repo, ["next"], { by: "dewey" }), /^start row 11/m);
ok(cli(repo, ["next", "rocko"]), /^nothing$/m);
no(cli(repo, ["next"]), 2, /next needs a seat/);
});
test("view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes", (t) => {
const repo = ready(t);
const first = ok(cli(repo, ["note", "9", "one", "--op", "note-9-000001"], { by: "darkwing" })).out.trim();
repo.g("checkout", "--", "docs/plans/QUEUE.md");
no(cli(repo, ["note", "9", "two", "--op", "note-9-000002"], { by: "darkwing" }), 2, /view stale: QUEUE.md shows rev 0; rev 1 \(op note-9-000001 by darkwing at .*\) is recorded .*Tell darkwing, then run `scripts\/mosaic queue render`/);
no(cli(repo, ["verify"]), 2, /view stale/);
const retry = ok(cli(repo, ["note", "9", "one", "--op", "note-9-000001"], { by: "darkwing" }));
assert.equal(retry.out.trim(), `${first} (already recorded at rev 1)`);
assert.match(retry.err, /warning: view stale/);
const read = ok(cli(repo, ["list"]));
assert.match(read.err, /warning: view stale/);
no(cli(repo, ["render", "--check"]), 2, /view stale/);
ok(cli(repo, ["render"]), /rendered rev 1 over rev 0; newly shown: rev 1 \(op note-9-000001 by darkwing/);
ok(cli(repo, ["render"]), /view current at rev 1; nothing written/);
ok(cli(repo, ["verify"]));
});
test("view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers", (t) => {
const repo = ready(t);
ok(cli(repo, ["note", "9", "one", "--op", "note-9-000001"], { by: "darkwing" }));
const good = readFileSync(repo.viewPath, "utf8");
const cases = [
good.replace("| one |", "| two |"),
repo.g("show", "HEAD:docs/plans/QUEUE.md").replace("Row six", "Row 6"),
good.replace("<!-- mosaic-queue:end -->\n", ""),
good + "<!-- mosaic-queue:begin -->\n",
];
for (const text of cases) {
writeFileSync(repo.viewPath, text);
no(cli(repo, ["note", "9", "two", "--op", "note-9-000002"], { by: "darkwing" }), 2, /view unknown: .*restore the table with git or re-apply the edit as queue ops/);
no(cli(repo, ["render"]), 2, /view unknown/);
no(cli(repo, ["verify"]), 2, /view unknown/);
assert.match(ok(cli(repo, ["show", "9"])).err, /warning: view unknown/);
assert.equal(readFileSync(repo.viewPath, "utf8"), text, "nothing rewrote an unknown view");
}
writeFileSync(repo.viewPath, good.replace("Header prose.", "Header prose, edited by hand."));
ok(cli(repo, ["note", "9", "two", "--op", "note-9-000002"], { by: "darkwing" }));
assert.match(readFileSync(repo.viewPath, "utf8"), /edited by hand/);
});
test("a hand edit to queue.json refuses every verb, reads included", (t) => {
const repo = ready(t);
const text = readFileSync(repo.queuePath, "utf8");
writeFileSync(repo.queuePath, text.replace('"piece": "Row six"', '"piece": "Row 6"'));
for (const args of [["list"], ["show", "6"], ["next", "darkwing"], ["verify"], ["render"], ["sync"]]) no(cli(repo, args), 2, /do not equal the replay/);
no(cli(repo, ["note", "9", "x", "--op", "note-9-000001"], { by: "darkwing" }), 2, /do not equal the replay/);
writeFileSync(repo.queuePath, text.replace("\n", "\n\n"));
no(cli(repo, ["list"]), 2, /re-serialize byte for byte/);
writeFileSync(repo.queuePath, "{ not json");
no(cli(repo, ["list"]), 2, /not valid JSON|is not JSON/);
});
test("verify and render --check leave bytes and mtimes unchanged", (t) => {
const repo = ready(t);
const snap = () => [repo.queuePath, repo.viewPath, join(repo.gitDir, "mosaic-queue.head")].map((p) => [readFileSync(p, "hex"), statSync(p, { bigint: true }).mtimeNs]);
const before = snap();
ok(cli(repo, ["verify"]));
ok(cli(repo, ["verify", "--current"]));
ok(cli(repo, ["render", "--check"]));
ok(cli(repo, ["render"]));
ok(cli(repo, ["list"]));
ok(cli(repo, ["next", "darkwing"]));
assert.deepEqual(snap(), before);
});
test("render is byte-stable across runs and repositories", (t) => {
const a = ready(t);
const b = ready(t);
const body = (repo) => readFileSync(repo.viewPath, "utf8").replace(repo.root, "ROOT");
assert.equal(body(a), body(b));
const bytes = readFileSync(a.viewPath);
writeFileSync(join(a.base, "x"), "");
ok(cli(a, ["render"]));
assert.ok(readFileSync(a.viewPath).equals(bytes));
});
test("snapshot and verify --snapshot", (t) => {
const repo = ready(t);
const out = join(repo.base, "snap");
mkdirSync(out);
no(cli(repo, ["snapshot", "--out", join(repo.root, "docs")]), 2, /is not empty/);
mkdirSync(join(repo.root, "empty-inside"));
no(cli(repo, ["snapshot", "--out", "empty-inside"]), 2, /is inside the repository/);
no(cli(repo, ["snapshot", "--out", join(repo.base, "missing")]), 2, /does not exist/);
ok(cli(repo, ["snapshot", "--out", out]), /^snapshot rev 0: queue.json [0-9a-f]{64}, QUEUE.md [0-9a-f]{64}$/m);
ok(cli(repo, ["verify", "--snapshot", out, "--base-absent"], { cwd: repo.base }), /genesis alone/);
no(cli(repo, ["verify", "--snapshot", out]), 4, /exactly one of/);
const base = join(repo.base, "base.json");
writeFileSync(base, readFileSync(repo.queuePath));
ok(cli(repo, ["note", "9", "one", "--op", "note-9-000001"], { by: "darkwing" }));
const out2 = join(repo.base, "snap2");
mkdirSync(out2);
ok(cli(repo, ["snapshot", "--out", out2]));
ok(cli(repo, ["verify", "--snapshot", out2, "--base-file", base], { cwd: repo.base }), /extends the base/);
no(cli(repo, ["verify", "--snapshot", out2, "--base-absent"]), 2, /must hold genesis alone/);
no(cli(repo, ["verify", "--snapshot", out, "--base-file", join(out2, "queue.json")]), 2, /does not extend the base/);
writeFileSync(join(out2, "QUEUE.md"), readFileSync(join(out, "QUEUE.md")));
no(cli(repo, ["verify", "--snapshot", out2, "--base-file", base]), 2, /is not the render of rev 1 \(stale\)/);
repo.g("checkout", "--", "docs/plans/QUEUE.md");
const out3 = join(repo.base, "snap3");
mkdirSync(out3);
no(cli(repo, ["snapshot", "--out", out3]), 2, /view stale/);
});
test("usage errors exit 4", (t) => {
const repo = ready(t);
no(cli(repo, []), 4);
no(cli(repo, ["frobnicate"]), 4, /unknown verb/);
no(cli(repo, ["list", "--bogus"]), 4, /unknown option --bogus/);
no(cli(repo, ["show"]), 4, /expected show ID/);
no(cli(repo, ["show", "0"]), 4, /positive integer/);
no(cli(repo, ["move", "9", "done", "--op", "x-00000001", "--op", "y-00000001"], { by: "sage" }), 4, /--op given twice/);
no(cli(repo, ["set", "9", "owner", "x", "--op", "set-9-000001"], { by: "sage" }), 4, /set fields/);
no(cli(repo, ["set", "9", "required", "yes", "--op", "set-9-000001"], { by: "sage" }), 4, /true or false/);
no(cli(repo, ["add", "--op", "add-0000001", "--piece", "x"], { by: "sage" }), 4, /add needs --gate/);
no(cli(repo, ["list", "--check"]), 4, /does not apply here/);
ok(cli(repo, ["--help"]), /^usage: queue list/);
});