Files
stack/packages/queue/tests/gate-owner.test.mjs
T
jason.woltjeandClaude Opus 5.5 fe27adea1c queue: pin the add retry order and drop row history from the gate-owner refusal (row 107, #1601)
A retried privileged add recorded with no gate owner answers with its
receipt before the --gate-owner check runs; a test pins that order and
a hoisting mutant fails it. The refusal no longer cites row 100.
SEMANTICS stays 2. Packet: agents/researcher/work/queue-107/ (manifest
b70dfe4a). Filbert approved round 1 in comment 28286. Sage's
canonical-root gate: queue 156/0, test-queue 30/0 with queue verify.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 17:16:51 -05:00

154 lines
7.9 KiB
JavaScript

// Row 101 (#1595): a privileged add names its gate owner. The refusal is a
// write-time check in store.mjs, so it is exercised through the CLI in a
// scratch repository. Replay is exercised through loadDoc, which runs the real
// replay path over a log that holds an old privileged add with no gate owner.
import assert from "node:assert/strict";
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { test } from "node:test";
import {
applyEntry, buildDoc, canonArgs, genesisReceipt, genesisRows, loadDoc, logDigest, parseMigrationMap, render, rowsArray, serialize, sha256, splitView,
} from "../src/queue.mjs";
import { QueueError } from "../src/errors.mjs";
import { MAP_ROWS, cli, genesisCommitted, mapText, scratchRepo } 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, 9, 13, 12, 0, clock++)).toISOString();
function brief() {
return { path: "docs/plans/brief-b.md", anchor: "Queue", blob: BLOB };
}
function addArgs(extra = {}) {
return { piece: "Gate row", gate: "g", brief: "docs/plans/brief-b.md#Queue", ...extra };
}
// A genesis document built the way store.mjs builds one.
function genesisDoc() {
const map = parseMigrationMap(mapText(MAP_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-gate-0001", t, "sage");
const entry = {
rev: 0, op: "genesis-gate-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-gate-0001", grows.length) },
viewSha: sha256(render(grows, 0)),
};
return { version: 1, canonicalRoot: ROOT, revision: 0, rows: grows, log: [entry] };
}
// Appends one add at the given semantics, the way store.mjs would have written
// it before this row: applyEntry only, no write-time check.
function addUnder(doc, by, args, semantics) {
const { state } = loadDoc(Buffer.from(serialize(doc)));
const rev = doc.revision + 1;
const entry = { rev, op: `add-gate-${rev}`, verb: "add", args: canonArgs("add", args), by, at: at(), semantics, result: null, viewSha: null };
const out = applyEntry(state, entry, { brief: brief() });
entry.result = out.result;
entry.viewSha = sha256(render(rowsArray(out.state), rev));
return buildDoc(doc.canonicalRoot, out.state, [...doc.log, entry]);
}
test("an old privileged add with no gate owner still replays through loadDoc, and gets jason", () => {
for (const semantics of [1, 2]) {
const d = addUnder(genesisDoc(), "sage", addArgs(), semantics);
const { doc, state } = loadDoc(Buffer.from(serialize(d)));
assert.equal(doc.rows.at(-1).gateOwner, "jason");
assert.equal(state.rows.get(doc.rows.at(-1).id).gateOwner, "jason");
}
});
test("a non-privileged add gets jason through the replay path and can't name a gate owner", () => {
const d = addUnder(genesisDoc(), "dewey", addArgs(), 2);
assert.equal(loadDoc(Buffer.from(serialize(d))).doc.rows.at(-1).gateOwner, "jason");
assert.throws(
() => addUnder(genesisDoc(), "dewey", addArgs({ gateOwner: "filbert" }), 2),
(err) => err instanceof QueueError && err.code === 2 && /only a privileged actor may set gateOwner on add/.test(err.message),
);
});
test("the CLI refuses a privileged add without --gate-owner, for jason and sage, and writes nothing", (t) => {
const repo = scratchRepo(t);
genesisCommitted(repo);
const queueBefore = readFileSync(repo.queuePath);
const viewBefore = readFileSync(repo.viewPath);
const revBefore = JSON.parse(queueBefore.toString("utf8")).revision;
const args = ["add", "--op", "add-gate-nogate-1", "--piece", "x", "--gate", "g", "--brief", "docs/plans/brief-b.md#Template"];
for (const by of ["sage", "jason"]) {
const r = cli(repo, args, { by });
assert.equal(r.code, 2, r.err);
assert.match(r.err, /--gate-owner/);
assert.match(r.err, /no default gate owner/);
assert.doesNotMatch(r.err, /row \d+/, "the refusal states the rule, not a row's history");
assert.deepEqual(readFileSync(repo.queuePath), queueBefore);
assert.deepEqual(readFileSync(repo.viewPath), viewBefore);
assert.equal(JSON.parse(readFileSync(repo.queuePath, "utf8")).revision, revBefore);
}
});
test("the CLI add with --gate-owner carries the named owner and records a log entry", (t) => {
const repo = scratchRepo(t);
genesisCommitted(repo);
const r = cli(repo, ["add", "--op", "add-gate-named-1", "--piece", "x", "--gate", "g", "--brief", "docs/plans/brief-b.md#Template", "--gate-owner", "filbert"], { by: "sage" });
assert.equal(r.code, 0, r.err);
const d = JSON.parse(readFileSync(repo.queuePath, "utf8"));
assert.equal(d.rows.at(-1).gateOwner, "filbert");
assert.equal(d.log.at(-1).op, "add-gate-named-1");
assert.equal(d.log.at(-1).by, "sage");
});
test("the CLI add by a non-privileged seat gets jason and can't name a gate owner", (t) => {
const repo = scratchRepo(t);
genesisCommitted(repo);
const base = ["--piece", "x", "--gate", "g", "--brief", "docs/plans/brief-b.md#Template"];
const ok = cli(repo, ["add", "--op", "add-gate-dewey-1", ...base], { by: "dewey" });
assert.equal(ok.code, 0, ok.err);
assert.equal(JSON.parse(readFileSync(repo.queuePath, "utf8")).rows.at(-1).gateOwner, "jason");
const before = readFileSync(repo.queuePath);
const refusedRun = cli(repo, ["add", "--op", "add-gate-dewey-2", ...base, "--gate-owner", "filbert"], { by: "dewey" });
assert.equal(refusedRun.code, 2, refusedRun.err);
assert.match(refusedRun.err, /only a privileged actor may set gateOwner on add/);
assert.deepEqual(readFileSync(repo.queuePath), before);
});
// Step 3 of the write path answers a recorded op before the gate-owner check
// runs. An add recorded before row 101 has a null gate owner, so a retry of it
// carries none either; hoisting the check above step 3 would refuse that retry.
// Every other retry test passes --gate-owner, so this one pins the order.
test("a retried sage add recorded with no gate owner answers (already recorded at rev N) and writes nothing", (t) => {
const repo = scratchRepo(t);
genesisCommitted(repo);
const before = JSON.parse(readFileSync(repo.queuePath, "utf8"));
const doc = addUnder(before, "sage", addArgs(), 2);
const entry = doc.log.at(-1);
assert.equal(entry.args.gateOwner, null);
assert.equal(entry.by, "sage");
// The state the old write path left: queue.json, the view and the witness all at the add's rev.
const bytes = Buffer.from(serialize(doc));
const parts = splitView(readFileSync(repo.viewPath, "utf8"));
writeFileSync(repo.queuePath, bytes);
writeFileSync(repo.viewPath, parts.head + render(doc.rows, entry.rev) + parts.tail);
writeFileSync(join(repo.gitDir, "mosaic-queue.head"), `${JSON.stringify({ revision: entry.rev, logDigest: logDigest(doc.log, entry.rev), fileSha: sha256(bytes), at: entry.at })}\n`);
const queueBefore = readFileSync(repo.queuePath);
const viewBefore = readFileSync(repo.viewPath);
const retry = ["add", "--op", entry.op, "--piece", "Gate row", "--gate", "g", "--brief", "docs/plans/brief-b.md#Queue"];
const r = cli(repo, retry, { by: "sage" });
assert.equal(r.code, 0, r.err);
assert.match(r.out, new RegExp(`\\(already recorded at rev ${entry.rev}\\)`));
assert.deepEqual(readFileSync(repo.queuePath), queueBefore);
assert.deepEqual(readFileSync(repo.viewPath), viewBefore);
assert.equal(JSON.parse(readFileSync(repo.queuePath, "utf8")).revision, entry.rev);
// The check still bites a new op with the same arguments.
const fresh = cli(repo, ["add", "--op", "add-gate-fresh-1", ...retry.slice(3)], { by: "sage" });
assert.equal(fresh.code, 2, fresh.err);
assert.match(fresh.err, /--gate-owner/);
assert.deepEqual(readFileSync(repo.queuePath), queueBefore);
});