Files
stack/packages/queue/src/store.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

800 lines
35 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The queue on disk: canonical checks (8.3), brief checks (8.13), the write
// path (8.5) and every verb. The CLI passes only its parsed arguments. Tests
// may also pass `io`, `proc`, `hook`, `now` and `readOrder` through this API
// to inject faults and pause at named steps; no flag or environment variable
// reaches them.
import { spawnSync } from "node:child_process";
import { lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
import { isAbsolute, join, resolve as resolvePath } from "node:path";
import { fileURLToPath } from "node:url";
import { QueueError } from "./errors.mjs";
import { checkPlatform, errno, fsyncFile, lstatOrNull, readOrNull, realIo, unlinkQuiet, writeTemp } from "./io.mjs";
import { acquire, checkGate, realProc, releaseOrWarn, unlock as unlockLock } from "./lock.mjs";
import {
PRIVILEGED, SEMANTICS, VERSION, applyEntry, buildDoc, canonArgs, checkCallerOpId, checkName, classifyView, countHeading,
describeUnshown, genesisReceipt, genesisRows, gitBlobId, loadDoc, logDigest, nextFor, parseBriefSpec, parseManifest,
parseMigrationMap, render, rowsArray, sameJson, serialize, sha256, splitView,
} from "./queue.mjs";
export const QUEUE_REL = "docs/plans/queue.json";
export const VIEW_REL = "docs/plans/QUEUE.md";
export const WITNESS_NAME = "mosaic-queue.head";
const CODE_FILE = fileURLToPath(import.meta.url);
const FIX = "scripts/mosaic queue";
function refuse(message) {
return new QueueError(message, 2);
}
function makeCtx(opts = {}) {
return {
cwd: opts.cwd ?? process.cwd(),
env: opts.env ?? process.env,
io: opts.io ?? realIo,
proc: opts.proc ?? realProc,
hook: opts.hook ?? (() => {}),
now: opts.now ?? (() => new Date().toISOString()),
readOrder: opts.readOrder ?? "witness-first",
lockWaitMs: opts.lockWaitMs ?? 10000,
lockStepMs: opts.lockStepMs ?? 100,
};
}
// --- git and the canonical checks (8.3) ---
function git(ctx, cwd, args, { allowFail = false } = {}) {
const r = spawnSync("git", ["-C", cwd, ...args], { env: ctx.env, maxBuffer: 64 << 20 });
if (r.error) throw new QueueError(`cannot run git: ${errno(r.error)}`, 1);
if (r.status !== 0) {
if (allowFail) return null;
throw new QueueError(`git ${args.join(" ")} failed: ${r.stderr.toString().trim()}`, 1);
}
return r.stdout;
}
function locate(ctx) {
for (const k of ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR"]) {
if (ctx.env[k] !== undefined) throw refuse(`${k} is set; the queue locates its repository only from the working directory`);
}
const raw = git(ctx, ctx.cwd, ["rev-parse", "--show-toplevel"], { allowFail: true });
if (raw === null) throw refuse(`${ctx.cwd} is not inside a git checkout`);
const top = realpathSync(raw.toString().trim());
const dirs = git(ctx, top, ["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]).toString().split("\n");
const gitDir = realpathSync(dirs[0]);
if (gitDir !== realpathSync(dirs[1])) throw refuse(`${top} is a linked worktree; the queue runs only in the canonical checkout`);
if (gitDir !== join(top, ".git")) throw refuse(`${top}'s git directory is ${gitDir}, not ${top}/.git`);
const ref = git(ctx, top, ["symbolic-ref", "-q", "HEAD"], { allowFail: true });
return {
top, gitDir, ref: ref === null ? null : ref.toString().trim(),
queuePath: join(top, QUEUE_REL), viewPath: join(top, VIEW_REL), docsDir: join(top, "docs/plans"), witnessPath: join(gitDir, WITNESS_NAME),
};
}
function checkBranch(loc, branch) {
if (loc.ref === null) throw refuse(`HEAD is detached; the queue runs only on branch ${branch}`);
if (loc.ref !== `refs/heads/${branch}`) throw refuse(`HEAD is ${loc.ref}; the queue runs only on branch ${branch}`);
}
function checkCode(root) {
if (!realpathSync(CODE_FILE).startsWith(`${root}/`)) throw refuse(`this queue code (${CODE_FILE}) is not under the canonical root ${root}`);
}
function checkCanonical(loc, root, branch) {
if (loc.top !== root) throw refuse(`this checkout is ${loc.top}; the queue's canonical root is ${root}`);
checkBranch(loc, branch);
checkCode(root);
}
function headHas(ctx, loc, path) {
return git(ctx, loc.top, ["cat-file", "-e", `HEAD:${path}`], { allowFail: true }) !== null;
}
function actorOf(ctx, by) {
const name = by ?? ctx.env.MOSAIC_AGENT_NAME;
if (name === undefined || name === "") throw refuse("no actor: pass --by NAME or set MOSAIC_AGENT_NAME");
return checkName(name, "actor");
}
// N12: --by wins over MOSAIC_AGENT_NAME, and a difference is worth a line on
// stderr. Both are self-asserted (J2), so nothing is logged.
function actorMismatch(ctx, by) {
const env = ctx.env.MOSAIC_AGENT_NAME;
if (by === null || by === undefined || env === undefined || env === "" || by === env) return null;
return `warning: --by ${JSON.stringify(by)} differs from MOSAIC_AGENT_NAME=${JSON.stringify(env)}`;
}
function isSeatDir(top, name) {
try {
return lstatSync(join(top, "agents", name)).isDirectory();
} catch {
return false;
}
}
function checkActorSeat(top, name) {
if (name !== "jason" && !isSeatDir(top, name)) throw refuse(`actor ${name} is not jason or a seat under agents/`);
}
function checkSeat(top, name, what, owner) {
if (name === "jason" || isSeatDir(top, name)) return;
if (owner && (name === "coordinator" || name === "unassigned")) return;
throw refuse(`${what} ${name} is not jason${owner ? ", coordinator, unassigned" : ""} or a seat under agents/`);
}
// Seat names checked when a write sets them; replay never looks at agents/.
function checkNewSeats(top, before, after) {
for (const row of after.values()) {
const old = before.get(row.id);
if (!old || old.owner !== row.owner) checkSeat(top, row.owner, `row ${row.id} owner`, true);
if (!old || old.gateOwner !== row.gateOwner) checkSeat(top, row.gateOwner, `row ${row.id} gate owner`, true);
if (!old || !sameJson(old.reviewers, row.reviewers)) row.reviewers.forEach((r) => checkSeat(top, r, `row ${row.id} reviewer`, false));
}
}
// --- brief and candidate checks (8.13, 8.9) ---
function headEntry(ctx, loc, path) {
const out = git(ctx, loc.top, ["ls-tree", "-z", "--full-tree", "HEAD", "--", path], { allowFail: true });
if (out === null) return null;
const entries = out.toString().split("\0").filter(Boolean).map((e) => {
const m = /^(\d{6}) (\w+) ([0-9a-f]{40})\t(.*)$/s.exec(e);
return m ? { mode: m[1], type: m[2], oid: m[3], path: m[4] } : null;
});
return entries.find((e) => e && e.path === path) ?? null;
}
export function briefCheck(ctx, loc, path, anchor) {
const abs = join(loc.top, path);
let st;
try {
st = lstatSync(abs);
} catch {
throw refuse(`brief ${path} does not exist in the working tree`);
}
if (st.isSymbolicLink() || !st.isFile()) throw refuse(`brief ${path} must be a regular file, not a symlink`);
if (!realpathSync(abs).startsWith(`${loc.top}/`)) throw refuse(`brief ${path} resolves outside ${loc.top}`);
const entry = headEntry(ctx, loc, path);
if (entry === null) throw refuse(`brief ${path} is not committed in HEAD; commit it first (a staged brief is refused)`);
if (entry.type !== "blob" || entry.mode === "120000") throw refuse(`brief ${path} is a ${entry.mode === "120000" ? "symlink" : entry.type} in HEAD, not a file`);
const text = git(ctx, loc.top, ["cat-file", "blob", entry.oid]).toString("utf8");
const n = countHeading(text, anchor);
if (n !== 1) throw refuse(`brief ${path} has ${n} headings "${anchor}" in HEAD; exactly one is required`);
return { path, anchor, blob: entry.oid };
}
// The working copy a seat reads (8.13): its git blob id, computed in-process.
function workingBriefMatches(loc, brief) {
try {
const abs = join(loc.top, brief.path);
if (!lstatSync(abs).isFile()) return false;
return gitBlobId(readFileSync(abs)) === brief.blob;
} catch {
return false;
}
}
function resolveCandidate(ctx, loc, spec) {
const path = isAbsolute(spec) ? spec : resolvePath(ctx.cwd, spec);
let st = null;
try { st = lstatSync(path); } catch { /* not a file: try it as a commit */ }
if (st !== null) {
if (!st.isFile()) throw refuse(`candidate ${spec} is not a regular file`);
let text;
try {
text = new TextDecoder("utf-8", { fatal: true }).decode(readFileSync(path));
} catch {
throw refuse(`candidate manifest ${spec} is not valid UTF-8`);
}
parseManifest(text);
return { kind: "manifest", digest: sha256(text), text };
}
const out = git(ctx, loc.top, ["rev-parse", "--verify", "--quiet", "--end-of-options", `${spec}^{commit}`], { allowFail: true });
if (out === null) throw refuse(`candidate ${spec} is neither a manifest file nor a commit`);
const sha = out.toString().trim();
const refs = git(ctx, loc.top, ["for-each-ref", "--contains", sha, "--format=%(refname)", "refs/heads", "refs/tags"]).toString().trim();
if (refs === "") throw refuse(`candidate commit ${sha} is not reachable from any local branch or tag`);
return { kind: "commit", digest: sha, text: null };
}
// --- files: queue.json, the witness, the view ---
function statKey(st) {
return { dev: st.dev, ino: st.ino, size: st.size, mtimeNs: st.mtimeNs };
}
function sameStat(a, b) {
return a.dev === b.dev && a.ino === b.ino && a.size === b.size && a.mtimeNs === b.mtimeNs;
}
// 8.5 step 1: bytes, stat, schema, byte-for-byte form, replay, canonical checks.
function readLocked(ctx, loc) {
const st = lstatOrNull(ctx.io, loc.queuePath);
if (st === null) throw refuse(`no ${QUEUE_REL}; genesis has not run`);
const bytes = ctx.io.readFile(loc.queuePath);
const d = loadDoc(bytes);
checkCanonical(loc, d.doc.canonicalRoot, d.branch);
return { bytes, stat: statKey(st), sha: sha256(bytes), doc: d.doc, state: d.state, branch: d.branch };
}
function parseWitness(bytes) {
if (bytes === null) return null;
let w;
try { w = JSON.parse(bytes.toString("utf8")); } catch { return { invalid: true }; }
const ok = w !== null && typeof w === "object" && Object.keys(w).join() === "revision,logDigest,fileSha,at"
&& Number.isInteger(w.revision) && w.revision >= 0 && /^[0-9a-f]{64}$/.test(w.logDigest) && /^[0-9a-f]{64}$/.test(w.fileSha)
&& typeof w.at === "string";
return ok ? w : { invalid: true };
}
function readWitness(ctx, loc) {
return parseWitness(readOrNull(ctx.io, loc.witnessPath));
}
// match, tail (the file extends the witness), lost, or absent. An invalid
// witness counts as absent.
export function compareWitness(doc, fileSha, w) {
if (w === null || w.invalid) return { state: "absent", invalid: Boolean(w?.invalid) };
if (w.revision > doc.revision || logDigest(doc.log, w.revision) !== w.logDigest) return { state: "lost", w };
if (w.revision === doc.revision) return w.fileSha === fileSha ? { state: "match", w } : { state: "lost", w };
return { state: "tail", w, from: w.revision + 1 };
}
function writeWitness(ctx, loc, doc, bytes) {
const rec = { revision: doc.revision, logDigest: logDigest(doc.log, doc.revision), fileSha: sha256(bytes), at: ctx.now() };
const tmp = `${loc.witnessPath}.tmp`;
unlinkQuiet(ctx.io, tmp);
writeTemp(ctx.io, tmp, Buffer.from(JSON.stringify(rec) + "\n"));
try {
ctx.io.rename(tmp, loc.witnessPath);
} catch (err) {
unlinkQuiet(ctx.io, tmp);
throw err;
}
try {
ctx.io.fsyncDir(loc.gitDir);
} catch (err) {
throw Object.assign(new Error(errno(err)), { code: err.code, renamed: true });
}
}
function confirmTail(ctx, loc, cur) {
try {
fsyncFile(ctx.io, loc.queuePath);
ctx.io.fsyncDir(loc.docsDir);
writeWitness(ctx, loc, cur.doc, cur.bytes);
} catch (err) {
throw new QueueError(`cannot confirm rev ${cur.doc.revision} durable (${errno(err)}); nothing changed`, 1);
}
}
// The refusal for lost history or a missing witness. A file holding genesis
// alone has one more way out: `sync` confirms it (8.5 step 2).
function lostRefusal(cmp, doc) {
const way = cmp.state === "absent" && doc.log.length === 1
? `run \`${FIX} sync\` (the file holds genesis alone)`
: `every verb refuses except \`${FIX} accept-history\` (see the README's manual recovery)`;
return refuse(`${lostMessage(cmp, doc)}; ${way}`);
}
function lostMessage(cmp, doc) {
if (cmp.state === "absent") {
return `the witness .git/${WITNESS_NAME} is ${cmp.invalid ? "invalid" : "missing"}; this file holds revs 0..${doc.revision}, and ops recorded after them may be lost`;
}
return `history lost: the witness recorded rev ${cmp.w.revision} (logDigest ${cmp.w.logDigest.slice(0, 12)}…, at ${cmp.w.at}); this file holds revs 0..${doc.revision} and does not extend it`;
}
// 8.5 step 2 for a locked caller. Returns the comparison, with `confirmed`
// listing the entries this call made durable.
function witnessStep(ctx, loc, cur, { verb, op = null, args = null }) {
const cmp = compareWitness(cur.doc, cur.sha, readWitness(ctx, loc));
const log = cur.doc.log;
const isRetry = (e) => e.op === op && e.verb === verb && sameJson(e.args, args);
if (cmp.state === "match") return { ...cmp, confirmed: [] };
if (cmp.state === "tail") {
const tail = log.slice(cmp.from);
if (verb === "sync" || tail.some(isRetry)) {
confirmTail(ctx, loc, cur);
return { ...cmp, confirmed: tail };
}
throw refuse(`unconfirmed tail: ${describeUnshown(tail)} visible but not confirmed durable; run \`${FIX} sync\` or retry that op`);
}
if (cmp.state === "absent" && log.length === 1 && (verb === "sync" || isRetry(log[0]))) {
confirmTail(ctx, loc, cur);
return { ...cmp, confirmed: log };
}
if (verb === "accept-history") return { ...cmp, confirmed: [] };
throw lostRefusal(cmp, cur.doc);
}
function readView(ctx, loc) {
const bytes = readOrNull(ctx.io, loc.viewPath);
return { bytes, text: bytes === null ? null : bytes.toString("utf8") };
}
function staleMessage(view, log) {
const last = log.length - 1;
const lines = view.unshown.map((e) => `rev ${e.rev} (op ${e.op} by ${e.by} at ${e.at})`);
return `view stale: QUEUE.md shows rev ${view.shown}; ${lines.join(", ")} ${view.unshown.length === 1 ? "is" : "are"} recorded but the table shows rev ${view.shown}`
+ ` and may never have been acknowledged. Tell ${[...new Set(view.unshown.map((e) => e.by))].join(", ")}, then run \`${FIX} render\` (the file is at rev ${last})`;
}
function unknownMessage(view) {
return `view unknown: ${view.why}; restore the table with git or re-apply the edit as queue ops, then run \`${FIX} render\``;
}
// 8.5 step 11. Returns null, or the warning that the view was not written.
function writeView(ctx, loc, before, parts, body, tag) {
const stale = `QUEUE.md changed since it was read; the view was not written and is stale. Check it, then run \`${FIX} render\``;
const now = readOrNull(ctx.io, loc.viewPath);
if (now === null || !now.equals(before)) return stale;
const tmp = `${loc.viewPath}.${tag}.tmp`;
let renamed = false;
try {
const mode = Number(ctx.io.stat(loc.viewPath).mode & 0o777n);
unlinkQuiet(ctx.io, tmp);
writeTemp(ctx.io, tmp, Buffer.from(parts.head + body + parts.tail), mode);
const again = readOrNull(ctx.io, loc.viewPath);
if (again === null || !again.equals(before)) {
unlinkQuiet(ctx.io, tmp);
return stale;
}
ctx.io.rename(tmp, loc.viewPath);
renamed = true;
ctx.io.fsyncDir(loc.docsDir);
return null;
} catch (err) {
if (renamed) return `the view is written but not confirmed durable (${errno(err)}); the op stands; after a host crash, check the table with \`${FIX} verify\``;
unlinkQuiet(ctx.io, tmp);
return `the view write failed (${errno(err)}); the op stands and the view is stale; run \`${FIX} render\``;
}
}
// 8.5 steps 6–9. `exclusive` (genesis) links instead of renaming, so an
// existing queue.json is never replaced.
function writeQueue(ctx, loc, cur, bytes, op, rev, exclusive = false) {
const { io } = ctx;
const tmp = `${loc.queuePath}.${op}.tmp`;
// Only the lock holder writes these; one left here is from a killed writer.
unlinkQuiet(io, tmp);
try {
writeTemp(io, tmp, bytes, 0o644);
} catch (err) {
throw new QueueError(`cannot write ${QUEUE_REL}.${op}.tmp (${errno(err)}); nothing changed`, 1);
}
ctx.hook("temp-written");
let unchanged;
try {
if (exclusive) {
unchanged = lstatOrNull(io, loc.queuePath) === null;
} else {
const st = lstatOrNull(io, loc.queuePath);
const now = st === null ? null : io.readFile(loc.queuePath);
unchanged = st !== null && sameStat(statKey(st), cur.stat) && now.equals(cur.bytes);
}
} catch (err) {
unlinkQuiet(io, tmp);
throw new QueueError(`cannot recheck ${QUEUE_REL} (${errno(err)}); nothing changed`, 1);
}
if (!unchanged) {
unlinkQuiet(io, tmp);
throw refuse(`${QUEUE_REL} changed outside the queue lock (git?) since it was read; nothing changed; retry the same op`);
}
try {
if (exclusive) {
io.link(tmp, loc.queuePath);
unlinkQuiet(io, tmp);
} else {
io.rename(tmp, loc.queuePath);
}
} catch (err) {
unlinkQuiet(io, tmp);
throw new QueueError(`cannot replace ${QUEUE_REL} (${errno(err)}); nothing changed`, 1);
}
ctx.hook("renamed");
try {
io.fsyncDir(loc.docsDir);
} catch (err) {
throw new QueueError(`uncertain ${op} rev ${rev}: visible, durability not confirmed (${errno(err)})`, 3);
}
ctx.hook("dir-synced");
}
function commitWrite(ctx, loc, cur, doc, bytes, op, view, body, exclusive) {
const rev = doc.revision;
writeQueue(ctx, loc, cur, bytes, op, rev, exclusive);
try {
writeWitness(ctx, loc, doc, bytes);
} catch (err) {
const what = err.renamed ? "witness written, its directory fsync failed" : "witness not updated";
throw new QueueError(`uncertain ${op} rev ${rev}: durable, ${what} (${errno(err)})`, 3);
}
ctx.hook("witnessed");
const warn = writeView(ctx, loc, view.bytes, view.parts, body, op);
ctx.hook("viewed");
return warn;
}
function withLock(ctx, loc, { op = null, verb }, fn) {
checkPlatform(ctx.io, [loc.docsDir, loc.gitDir]);
const handle = acquire({ gitDir: loc.gitDir, io: ctx.io, proc: ctx.proc, op, verb, waitMs: ctx.lockWaitMs, stepMs: ctx.lockStepMs, hook: ctx.hook });
const res = { out: [], err: [], code: 0 };
let failure = null;
try {
ctx.hook("locked");
fn(res);
} catch (err) {
failure = err;
}
const msg = releaseOrWarn(handle, ctx.io);
// A refusal still reports what release found (8.4).
if (msg && failure instanceof Error) failure.message += `\nwarning: ${msg}`;
else if (msg) res.err.push(`warning: ${msg}`);
if (failure) throw failure;
return res;
}
function checkCallerOp(op) {
if (typeof op !== "string" || op === "") throw new QueueError("--op ID is required; choose it before the first attempt and reuse it on every retry", 4);
checkCallerOpId(op);
}
// --- mutations ---
function resolveFor(ctx, loc, cur, verb, args, cmp) {
if (verb === "add") {
const { path, anchor } = parseBriefSpec(args.brief);
return { brief: briefCheck(ctx, loc, path, anchor) };
}
if (verb === "set" && args.field === "brief") {
const { path, anchor } = parseBriefSpec(args.value);
return { brief: briefCheck(ctx, loc, path, anchor) };
}
if (verb === "move") {
const row = cur.state.rows.get(args.id);
if (args.candidate !== null && row?.state === "in-progress" && args.to === "in-review") return { candidate: resolveCandidate(ctx, loc, args.candidate) };
return { candidate: null };
}
if (verb === "accept-history") return { oldWitness: cmp.w ? { revision: cmp.w.revision, logDigest: cmp.w.logDigest } : null };
return {};
}
// Every logged verb except genesis. `yes` is accept-history's confirmation;
// it is not part of the op's identity.
export function mutate(opts, { verb, op, args, by, yes = false }) {
const ctx = makeCtx(opts);
const mismatch = actorMismatch(ctx, by);
try {
const res = mutateAs(ctx, { verb, op, args, by, yes });
if (mismatch) res.err.unshift(mismatch);
return res;
} catch (err) {
if (mismatch && err instanceof Error) err.message += `\n${mismatch}`;
throw err;
}
}
function mutateAs(ctx, { verb, op, args, by, yes }) {
checkCallerOp(op);
const actor = actorOf(ctx, by);
const cargs = canonArgs(verb, args);
if (verb === "genesis") return genesis(ctx, op, cargs, actor);
const loc = locate(ctx);
checkActorSeat(loc.top, actor);
if (!headHas(ctx, loc, QUEUE_REL)) throw refuse(`genesis not committed: HEAD has no ${QUEUE_REL}; commit genesis with \`scripts/queue-commit.sh --genesis\` first`);
return withLock(ctx, loc, { op, verb }, (res) => {
const cur = readLocked(ctx, loc);
const cmp = witnessStep(ctx, loc, cur, { verb, op, args: cargs });
for (const e of cmp.confirmed) res.err.push(`durable now, never acknowledged: ${e.op} by ${e.by} at ${e.at}`);
// Step 3: a recorded op answers with its receipt, before any view check.
const prior = cur.doc.log.find((e) => e.op === op);
if (prior) {
if (prior.verb !== verb || !sameJson(prior.args, cargs)) {
throw refuse(`op ${op} is recorded at rev ${prior.rev} as ${prior.verb} with other arguments; a new operation needs a new op id`);
}
const view = classifyView(readView(ctx, loc).text, cur.doc.log);
if (view.state === "stale") res.err.push(`warning: ${staleMessage(view, cur.doc.log)}`);
if (view.state === "unknown") res.err.push(`warning: ${unknownMessage(view)}`);
res.out.push(`${prior.result.receipt} (already recorded at rev ${prior.rev})`);
return;
}
if (verb === "accept-history") {
if (cmp.state !== "lost" && cmp.state !== "absent") throw refuse("history is not lost and the witness is present; accept-history has nothing to accept");
const range = `${lostMessage(cmp, cur.doc)}. ops in that range are no longer deduplicated`;
if (!yes) throw refuse(`${range}. Re-run with --yes to accept this file as the queue's history`);
res.err.push(range);
}
// Step 4.
const viewRead = readView(ctx, loc);
const view = classifyView(viewRead.text, cur.doc.log);
if (view.state === "unknown") throw refuse(unknownMessage(view));
if (view.state === "stale" && verb !== "accept-history") throw refuse(staleMessage(view, cur.doc.log));
// Step 5.
const resolved = resolveFor(ctx, loc, cur, verb, cargs, cmp);
const rev = cur.doc.revision + 1;
const entry = { rev, op, verb, args: cargs, by: actor, at: ctx.now(), semantics: SEMANTICS, result: null, viewSha: null };
const applied = applyEntry({ ...cur.state, revision: cur.doc.revision }, entry, resolved);
if (verb === "move" && applied.result.from === "briefed" && applied.result.to === "in-progress") {
const row = cur.state.rows.get(cargs.id);
if (!workingBriefMatches(loc, row.brief)) throw refuse(`row ${row.id}: brief differs from pinned blob; ask the lead to re-pin (${row.brief.path}, pinned ${row.brief.blob})`);
}
checkNewSeats(loc.top, cur.state.rows, applied.state.rows);
entry.result = applied.result;
const body = render(rowsArray(applied.state), rev);
entry.viewSha = sha256(body);
const doc = buildDoc(cur.doc.canonicalRoot, applied.state, [...cur.doc.log, entry]);
const bytes = Buffer.from(serialize(doc));
loadDoc(bytes);
const warn = commitWrite(ctx, loc, cur, doc, bytes, op, { bytes: viewRead.bytes, parts: view.parts }, body, false);
if (warn) res.err.push(`warning: ${warn}`);
res.out.push(entry.result.receipt);
});
}
// log[0] (8.2). Runs before canonicalRoot exists; its arguments are checked
// against this checkout instead.
function genesis(ctx, op, args, actor) {
if (!PRIVILEGED.has(actor)) throw refuse("only a privileged actor (jason or sage) may run genesis");
const loc = locate(ctx);
checkActorSeat(loc.top, actor);
if (args.root !== loc.top) throw refuse(`--root ${args.root} is not this checkout's toplevel ${loc.top}`);
checkBranch(loc, args.branch);
checkCode(loc.top);
return withLock(ctx, loc, { op, verb: "genesis" }, (res) => {
if (lstatOrNull(ctx.io, loc.queuePath) !== null) {
const cur = readLocked(ctx, loc);
const g = cur.doc.log[0];
if (g.op !== op || !sameJson(g.args, args)) throw refuse(`${QUEUE_REL} exists; genesis runs once`);
const cmp = witnessStep(ctx, loc, cur, { verb: "genesis", op, args });
for (const e of cmp.confirmed) res.err.push(`durable now, never acknowledged: ${e.op} by ${e.by} at ${e.at}`);
res.out.push(`${g.result.receipt} (already recorded at rev 0)`);
return;
}
if (headHas(ctx, loc, QUEUE_REL)) throw refuse(`HEAD already has ${QUEUE_REL}; genesis runs once`);
if (lstatOrNull(ctx.io, loc.witnessPath) !== null) throw refuse(`the witness .git/${WITNESS_NAME} exists without ${QUEUE_REL}; diagnose by hand`);
const mapEntry = headEntry(ctx, loc, args.map);
if (mapEntry === null || mapEntry.type !== "blob" || mapEntry.mode === "120000") throw refuse(`the migration map ${args.map} is not a committed file in HEAD`);
const map = parseMigrationMap(git(ctx, loc.top, ["cat-file", "blob", mapEntry.oid]).toString("utf8"));
const blobs = new Map();
for (const r of map.rows) {
if (r.brief !== null) blobs.set(r.id, briefCheck(ctx, loc, r.brief.path, r.brief.anchor).blob);
}
const viewRead = readView(ctx, loc);
const parts = viewRead.text === null ? null : splitView(viewRead.text);
if (parts === null) throw refuse(`${VIEW_REL} needs the two queue markers, once each and in order`);
const at = ctx.now();
const rows = genesisRows(map, blobs, op, at, actor);
checkNewSeats(loc.top, new Map(), new Map(rows.map((r) => [r.id, r])));
const body = render(rows, 0);
const result = {
mapBlob: mapEntry.oid, highWater: map.highWater, retired: [...map.retired].sort((a, b) => a - b), rows,
legacyView: parts.body, receipt: genesisReceipt(op, rows.length),
};
const entry = { rev: 0, op, verb: "genesis", args, by: actor, at, semantics: SEMANTICS, result, viewSha: sha256(body) };
const doc = { version: VERSION, canonicalRoot: args.root, revision: 0, rows, log: [entry] };
const bytes = Buffer.from(serialize(doc));
loadDoc(bytes);
const warn = commitWrite(ctx, loc, null, doc, bytes, op, { bytes: viewRead.bytes, parts }, body, true);
if (warn) res.err.push(`warning: ${warn}`);
res.out.push(result.receipt);
});
}
// --- reads without the lock (8.4) ---
function readUnlocked(ctx, loc) {
const { io } = ctx;
let w;
let bytes;
if (ctx.readOrder === "file-first") {
bytes = readOrNull(io, loc.queuePath);
ctx.hook("reader-between");
w = parseWitness(readOrNull(io, loc.witnessPath));
} else {
w = parseWitness(readOrNull(io, loc.witnessPath));
ctx.hook("reader-between");
bytes = readOrNull(io, loc.queuePath);
}
const notes = [];
if (bytes !== null) {
let d = null;
try { d = loadDoc(bytes); } catch (err) { if (!(err instanceof QueueError)) throw err; }
if (d !== null) {
checkCanonical(loc, d.doc.canonicalRoot, d.branch);
const cmp = compareWitness(d.doc, sha256(bytes), w);
if (cmp.state === "match" || cmp.state === "tail") {
if (cmp.state === "tail") notes.push(`rev ${d.doc.revision} visible, not confirmed durable`);
return { doc: d.doc, state: d.state, notes };
}
}
}
// Adverse: recheck under the lock before reporting anything.
let out = null;
const res = withLock(ctx, loc, { verb: "read" }, () => {
const cur = readLocked(ctx, loc);
const cmp = compareWitness(cur.doc, cur.sha, readWitness(ctx, loc));
if (cmp.state === "tail") notes.push(`unconfirmed tail: ${describeUnshown(cur.doc.log.slice(cmp.from))} visible but not confirmed durable; run \`${FIX} sync\``);
else if (cmp.state !== "match") throw lostRefusal(cmp, cur.doc);
out = { doc: cur.doc, state: cur.state };
});
return { ...out, notes: [...notes, ...res.err] };
}
function viewNotes(ctx, loc, log) {
const view = classifyView(readView(ctx, loc).text, log);
if (view.state === "stale") return [`warning: ${staleMessage(view, log)}`];
if (view.state === "unknown") return [`warning: ${unknownMessage(view)}`];
return [];
}
function readState(opts) {
const ctx = makeCtx(opts);
const loc = locate(ctx);
const r = readUnlocked(ctx, loc);
return { ctx, loc, ...r, notes: [...r.notes, ...viewNotes(ctx, loc, r.doc.log)] };
}
export function list(opts) {
const r = readState(opts);
const out = rowsArray(r.state).map((row) => `${row.id}\t${row.state}\t${row.owner}\t${row.piece}`);
return { out, err: r.notes, code: 0 };
}
export function show(opts, id) {
const r = readState(opts);
const row = r.state.rows.get(id);
if (!row) throw refuse(`no row ${id}`);
return { out: [JSON.stringify(row, null, 2)], err: r.notes, code: 0 };
}
export function next(opts, seat) {
const ctx = makeCtx(opts);
const name = seat ?? ctx.env.MOSAIC_AGENT_NAME;
if (name === undefined || name === "") throw refuse("next needs a seat: pass SEAT or set MOSAIC_AGENT_NAME");
checkName(name, "seat");
const r = readState(opts);
const n = nextFor(r.state.rows, name, (row) => workingBriefMatches(r.loc, row.brief));
if (n.action === "nothing") return { out: ["nothing"], err: r.notes, code: 0 };
const row = n.row;
const brief = row.brief ? `; brief ${row.brief.path} § ${row.brief.anchor}` : "";
const flag = n.briefDiffers ? "; brief differs from pinned blob; ask the lead to re-pin" : "";
return { out: [`${n.action} row ${row.id}: ${row.piece}${brief}${flag}`], err: r.notes, code: 0 };
}
// --- locked verbs that log nothing ---
function lockedRead(opts, verb, fn) {
const ctx = makeCtx(opts);
const loc = locate(ctx);
return withLock(ctx, loc, { verb }, (res) => {
const cur = readLocked(ctx, loc);
fn(ctx, loc, cur, res);
});
}
export function sync(opts, op = null) {
if (op !== null) checkCallerOp(op);
return lockedRead(opts, "sync", (ctx, loc, cur, res) => {
const cmp = witnessStep(ctx, loc, cur, { verb: "sync" });
if (cmp.confirmed.length === 0) res.out.push(`nothing to confirm: rev ${cur.doc.revision} is durable and witnessed`);
for (const e of cmp.confirmed) res.out.push(`durable now, never acknowledged: ${e.op} by ${e.by} at ${e.at}`);
res.err.push(...viewNotes(ctx, loc, cur.doc.log));
});
}
export function renderView(opts, { check = false } = {}) {
return lockedRead(opts, "render", (ctx, loc, cur, res) => {
witnessStep(ctx, loc, cur, { verb: "render" });
const log = cur.doc.log;
const viewRead = readView(ctx, loc);
const view = classifyView(viewRead.text, log);
if (view.state === "unknown") throw refuse(unknownMessage(view));
if (view.state === "current") {
res.out.push(`view current at rev ${cur.doc.revision}${check ? "" : "; nothing written"}`);
return;
}
if (check) throw refuse(staleMessage(view, log));
const body = render(rowsArray(cur.state), cur.doc.revision);
const warn = writeView(ctx, loc, viewRead.bytes, view.parts, body, "render");
if (warn) throw new QueueError(warn, 1);
res.out.push(`rendered rev ${cur.doc.revision} over rev ${view.shown}; newly shown: ${describeUnshown(view.unshown)}`);
});
}
function briefDrift(ctx, loc, rows) {
const drift = [];
for (const row of rowsArray({ rows })) {
if (row.brief === null || row.state === "done" || row.state === "parked") continue;
const e = headEntry(ctx, loc, row.brief.path);
if (e === null || e.type !== "blob" || e.mode === "120000") {
drift.push(`row ${row.id}: brief ${row.brief.path} is no longer a file in HEAD`);
} else if (e.oid !== row.brief.blob) {
drift.push(`row ${row.id}: brief ${row.brief.path} changed in HEAD (pinned ${row.brief.blob}, HEAD ${e.oid})`);
}
}
return drift;
}
export function verify(opts, { current = false } = {}) {
return lockedRead(opts, "verify", (ctx, loc, cur, res) => {
witnessStep(ctx, loc, cur, { verb: "verify" });
const view = classifyView(readView(ctx, loc).text, cur.doc.log);
if (view.state === "unknown") throw refuse(unknownMessage(view));
if (view.state === "stale") throw refuse(staleMessage(view, cur.doc.log));
if (current) {
const drift = briefDrift(ctx, loc, cur.state.rows);
if (drift.length) throw refuse(`brief drift against HEAD:\n${drift.join("\n")}`);
}
res.out.push(`ok verify rev ${cur.doc.revision}: file valid, witness matches, view current${current ? ", briefs match HEAD" : ""}`);
});
}
// 8.12 step 4. Reads only DIR/queue.json, DIR/QUEUE.md and the base: no git,
// no working directory, no lock. It does not certify witness continuity.
export function verifySnapshot(dir, { baseFile = null, baseAbsent = false } = {}) {
if (baseAbsent === (baseFile !== null)) throw new QueueError("verify --snapshot needs exactly one of --base-file F or --base-absent", 4);
const bytes = readFileOrRefuse(join(dir, "queue.json"));
const viewText = readFileOrRefuse(join(dir, "QUEUE.md")).toString("utf8");
const { doc } = loadDoc(bytes);
const view = classifyView(viewText, doc.log);
if (view.state !== "current") throw refuse(`snapshot QUEUE.md is not the render of rev ${doc.revision} (${view.state})`);
if (baseAbsent) {
if (doc.log.length !== 1) throw refuse(`the base is absent, so the snapshot must hold genesis alone; it holds revs 0..${doc.revision}`);
} else {
const base = loadDoc(readFileOrRefuse(baseFile)).doc;
if (base.canonicalRoot !== doc.canonicalRoot) throw refuse("the snapshot's canonicalRoot differs from the base's");
if (doc.revision < base.revision || logDigest(doc.log, base.revision) !== logDigest(base.log, base.revision)) {
throw refuse(`the snapshot's log does not extend the base's (base rev ${base.revision}, snapshot rev ${doc.revision})`);
}
}
return { out: [`ok verify --snapshot rev ${doc.revision}: pair valid, view current, ${baseAbsent ? "genesis alone" : "extends the base"}`], err: [], code: 0 };
}
function readFileOrRefuse(path) {
try {
return readFileSync(path);
} catch (err) {
throw refuse(`cannot read ${path} (${errno(err)})`);
}
}
export function snapshot(opts, outDir) {
const ctx = makeCtx(opts);
const dir = resolvePath(ctx.cwd, outDir);
let real;
try { real = realpathSync(dir); } catch { throw refuse(`--out ${dir} does not exist`); }
if (!lstatSync(real).isDirectory()) throw refuse(`--out ${dir} is not a directory`);
if (readdirSync(real).length !== 0) throw refuse(`--out ${dir} is not empty`);
return lockedRead(opts, "snapshot", (c, loc, cur, res) => {
if (real === loc.top || real.startsWith(`${loc.top}/`)) throw refuse(`--out ${dir} is inside the repository`);
witnessStep(c, loc, cur, { verb: "snapshot" });
const viewRead = readView(c, loc);
const view = classifyView(viewRead.text, cur.doc.log);
if (view.state !== "current") throw refuse(view.state === "stale" ? staleMessage(view, cur.doc.log) : unknownMessage(view));
writeTemp(c.io, join(real, "queue.json"), cur.bytes);
writeTemp(c.io, join(real, "QUEUE.md"), viewRead.bytes);
res.out.push(`snapshot rev ${cur.doc.revision}: queue.json ${cur.sha}, QUEUE.md ${sha256(viewRead.bytes)}`);
});
}
// --- the lock by hand ---
function unlockLoc(ctx) {
const loc = locate(ctx);
const bytes = readOrNull(ctx.io, loc.queuePath);
if (bytes !== null) {
let d = null;
try { d = loadDoc(bytes); } catch { /* an invalid file does not keep a dead lock in place */ }
if (d !== null) checkCanonical(loc, d.doc.canonicalRoot, d.branch);
}
checkPlatform(ctx.io, [loc.gitDir]);
return loc;
}
export function unlock(opts, { checkGateOnly = false } = {}) {
const ctx = makeCtx(opts);
const loc = unlockLoc(ctx);
if (checkGateOnly) return { out: [checkGate({ gitDir: loc.gitDir, io: ctx.io, proc: ctx.proc }).line], err: [], code: 0 };
const { result, warning } = unlockLock({ gitDir: loc.gitDir, io: ctx.io, proc: ctx.proc, hook: ctx.hook });
return { out: [result], err: warning ? [`warning: ${warning}`] : [], code: 0 };
}