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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-09-26 20:14:09 -05:00
co-authored by Claude Opus 5.5
parent c9539baa0f
commit 6ca116b7ba
32 changed files with 4886 additions and 103 deletions
+26 -8
View File
@@ -9,9 +9,9 @@ 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, release, unlock as unlockLock } from "./lock.mjs";
import { acquire, checkGate, realProc, releaseOrWarn, unlock as unlockLock } from "./lock.mjs";
import {
CALLER_OP_RE, PRIVILEGED, SEMANTICS, VERSION, applyEntry, buildDoc, canonArgs, checkName, classifyView, countHeading,
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";
@@ -95,6 +95,14 @@ function actorOf(ctx, by) {
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();
@@ -417,8 +425,7 @@ function withLock(ctx, loc, { op = null, verb }, fn) {
} catch (err) {
failure = err;
}
let msg;
try { msg = release(handle, ctx.io); } catch (err) { msg = `cannot release the queue lock (${errno(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}`);
@@ -428,8 +435,7 @@ function withLock(ctx, loc, { op = null, verb }, fn) {
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);
if (!CALLER_OP_RE.test(op)) throw refuse(`op id ${JSON.stringify(op)} must match ${CALLER_OP_RE.source} (8 to 72 characters)`);
if (op.endsWith(".outcome")) throw refuse(`op ids ending in .outcome are reserved`);
checkCallerOpId(op);
}
// --- mutations ---
@@ -456,6 +462,18 @@ function resolveFor(ctx, loc, cur, verb, args, cmp) {
// 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);
@@ -776,6 +794,6 @@ 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 [line, ...warnings] = unlockLock({ gitDir: loc.gitDir, io: ctx.io, proc: ctx.proc, hook: ctx.hook }).split("\n");
return { out: [line], err: warnings, 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 };
}