newspaper_wedding/.aidlc/tools/aidlc-swarm.ts
Andrew Ridgway bec1eaac87
Some checks failed
Test / test (push) Has been cancelled
first pass at the newspaper builder
2026-09-14 11:57:22 +10:00

2403 lines
84 KiB
TypeScript

// Swarm convergence referee — the deterministic verdict surface the conductor consults.
//
// The swarm fires only under human-granted Construction autonomy, inside a live
// Claude Code session. That session — the conductor — owns the fan-out (N parallel
// Task calls, or an inline Dynamic Workflow when AIDLC_USE_SWARM=1) and the retry
// loop. A bun subprocess cannot issue Task calls, so the worker-dispatch layer is
// NOT here. What lives here is everything that must be deterministic: the
// convergence verdict, the anti-tamper guard, the serialised merge-back, the audit
// taxonomy, and the typed failure envelope.
//
// THE SPLIT (three concerns): the conductor owns fan-out + loop drive (knowledge);
// this tool owns the convergence verdict + merge + audit (determinism); the human
// grants autonomy and takes the baton on the envelope (judgement).
//
// THREE STATELESS SUBCOMMANDS (no iteration counter, no persisted state):
// prepare --batch <n> --units <a,b,c> [--base <branch>] [--concurrency <n>]
// [--degraded-from <subagent|ultracode>] [--repo <name>]
// Fork an isolated git worktree per unit (aidlc-worktree create +
// aidlc-bolt start --worktree) and emit SWARM_STARTED once for the units
// whose worktrees were successfully prepared.
// --repo (P7) selects the sibling repo the batch's worktrees fork inside (a
// multi-repo intent requires it; single-repo infers the lone repo); the
// resolved name is forwarded to every aidlc-worktree create + bolt start.
// The anti-tamper baseline is each worktree's OWN git fork (HEAD) — nothing
// is stored; check/finalize re-derive the pristine bytes with `git diff
// --quiet HEAD`. Runs before any worker, so it cannot fold into check.
// --degraded-from records a loud downgrade (AIDLC_USE_SWARM=1 but the
// Workflow tool was unavailable, so the conductor ran the subagent floor):
// emits SWARM_DEGRADED. The driver-SELECTION read (AIDLC_USE_SWARM) is
// conductor-side — this tool only learns a degrade happened via the flag.
// check <unit> --check-cmd <cmd> [--test-file <path>]
// Stateless single-unit verdict: the project's check command (exit 0 = green,
// the AUTHORITATIVE signal — a worker's own success claim is never trusted)
// plus an anti-tamper compare of the protected file against its forked-git
// baseline. Prints {unit, converged, tampered, reason}; exits 0 iff the unit
// is GENUINELY converged (green AND untampered), non-zero otherwise. Emits
// no audit — it informs the conductor's retry decision (knowledge), it does
// not commit anything. Same input → same verdict, however many times called.
// finalize --batch <n> --units <a,b,c> --claimed <a,b> --check-cmd <cmd>
// [--test-file <path>] [--reasons <unit>=<reason>,...]
// The AUTHORITATIVE gate. The conductor's claimed-converged set is an
// explicit input and the only thing finalize trusts from it. For each
// claimed unit, RE-RUN the check (green + untampered) and, when the current
// stage declares a reviewer, require that unit's matching post-BOLT_STARTED
// REVIEW_COMPLETED receipt before any merge. A unit named in --claimed but
// red or unreviewed on disk is refused the merge and lands in the failure
// envelope (the lying-conductor guard). Serialised HOLD-MERGE merge-back of
// the genuine passes only, then emit the full SWARM_* audit trail + the typed
// envelope + exit 0/2. --reasons carries the conductor's
// typed attribution for a DECLINED (unclaimed) unit — unsatisfiable /
// budget-exhausted / cap-exhausted — recorded faithfully (the conductor
// judges WHY a unit gave up; the tool only records it, never for a claimed
// unit, whose reason is always the tool's own re-verify verdict).
//
// WHY STATELESS / NO CAP CONSTANT. "The cap" is three jobs on three concerns — the
// verdict (determinism -> check), the retry decision (knowledge -> the conductor,
// which judges "one more try vs unsatisfiable"), and the runaway backstop
// (determinism -> the harness 8-block Stop-hook ceiling). A per-unit counter here
// would make determinism do the knowledge job and is redundant on the other
// drivers (the ultracode script's cap is its `for`-bound; /goal's is its
// turn-clause). So this tool holds none of it: check is advisory, finalize is
// authoritative (re-verifies at the merge gate), so a red unit cannot merge even
// if the conductor lies or misremembers.
//
// COMPOSES existing tools, does NOT reimplement them:
// - aidlc-worktree create -> the isolated git worktree per unit
// - aidlc-bolt start --worktree -> state/audit/runtime-graph fork into it
// - aidlc-bolt complete --merge -> the AIDLC-data merge back to the base
// - aidlc-bolt release-merge -> release the existing per-Bolt HOLD-MERGE
// lock before a serialised merge (idempotent — safe if never held). The merge
// phase is serial (a one-at-a-time loop), so only one merge is ever in flight.
// - aidlc-bolt fail -> close a failed unit's Bolt lifecycle
// (BOLT_FAILED paired with the BOLT_STARTED that `start --worktree` emitted).
import { spawnSync } from "node:child_process";
import { createHash, randomUUID } from "node:crypto";
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { basename, dirname, join, posix, relative, resolve, sep } from "node:path";
import { fileURLToPath } from "node:url";
import { appendAuditEntry } from "./aidlc-audit.ts";
import {
assertNoSymlinkInChainOrThrow,
auditBlockField,
auditShardDir,
boltSlugForUnit,
filterProducesByKind,
filteredRawIndexEntries,
findAllEvents,
getField,
isRegularFile,
latestMainWorkflowStageRunFloor,
latestMainWorkflowStageRunFloorForProject,
parseArgs,
parseSourceListing,
readAuditShardEvents,
readUnitSourceManifest,
readUnitSourceSnapshot,
readRegularFileNoFollowOrThrow,
readStateFile,
recordDir,
relativeRecordDir,
reviewArtifactFingerprint,
reviewArtifactBytesSnapshot,
reviewedSourceRef,
resolveAuditWorktreePath,
resolveBoltDag,
resolveConstructionRepo,
resolveProjectDir,
resolveStage,
sourceListingSha256,
shapeSourceSnapshotIndex,
sourceClaimCovers,
sourceListingEntriesEqual,
type SourceClaimModel,
UNBINDABLE_FINGERPRINT,
validateUnitName,
worktreeAuditFilePath,
worktreePath,
worktreeRuntimeGraphPath,
workspaceSourceEmbeddedGitPaths,
workspaceSourceFingerprint as worktreeSourceFingerprint,
workspaceSourceListing,
workspaceSourceSnapshotPaths,
worktreeStateFilePath,
worktreeReviewAttemptProjection,
writeBufferAtomic,
} from "./aidlc-lib.ts";
import { compiledExecutable } from "./aidlc-runtime-paths.ts";
import {
beginCodeGeneration,
evaluateCodeGenerationApproval,
} from "./aidlc-testing-posture.ts";
const TOOLS_DIR = dirname(fileURLToPath(import.meta.url));
// The typed reason enum the conductor branches on. budget-exhausted stays valid
// for the ultracode driver's token ceiling; cap-exhausted is the loop-ended-
// without-convergence sense; error covers a tamper / lying-claim / plumbing fault.
type FailureReason = "unsatisfiable" | "budget-exhausted" | "cap-exhausted" | "error";
// The driver the conductor degraded away from (records the loud downgrade).
type DriverName = "subagent" | "ultracode";
const DRIVER_VALUES: DriverName[] = ["subagent", "ultracode"];
// The typed reasons the conductor may attribute to a DECLINED unit (one it did
// not claim converged). Judging WHICH applies is the conductor's knowledge call
// (D-I) — the tool only records it, exactly as it records --claimed and
// --degraded-from. `error` is excluded: it is the tool's OWN verdict for a
// claimed-but-red / tampered unit, never a conductor-supplied attribution.
const DECLINED_REASONS: FailureReason[] = ["unsatisfiable", "budget-exhausted", "cap-exhausted"];
interface UnitResult {
unit: string;
status: "converged" | "failed";
reason?: FailureReason;
detail?: string;
tampered?: boolean;
}
interface SourceBinding {
fingerprint: string;
commit: string;
}
interface ReceiptCheck {
error: string | null;
artifactFingerprint?: string;
sourceFingerprint?: string;
unitSourceFingerprint?: string;
}
interface ReviewedRecordSnapshotEntry {
logicalPath: string;
bytes: Buffer | null;
}
interface ReviewedRecordSnapshot {
entries: ReviewedRecordSnapshotEntry[];
}
interface SwarmAttemptStamp {
stage: string;
floor: string;
}
// --- Sibling-tool composition (synchronous; these calls are quick) ----------
interface ToolRun {
ok: boolean;
stdout: string;
stderr: string;
}
function runTool(toolFile: string, args: string[], projectDir: string): ToolRun {
const executable = compiledExecutable();
const noun = toolFile.replace(/^aidlc-/, "").replace(/\.ts$/, "");
const command = executable
? [executable, "engine", noun, ...args, "--project-dir", projectDir]
: [process.execPath, join(TOOLS_DIR, toolFile), "--project-dir", projectDir, ...args];
const result = spawnSync(command[0], command.slice(1), {
encoding: "utf-8",
cwd: projectDir,
timeout: 60_000,
env: { ...process.env, AIDLC_PROJECT_DIR: projectDir },
});
return {
ok: result.status === 0,
stdout: result.stdout ?? "",
stderr: result.stderr ?? "",
};
}
// --- The deterministic verdict primitives -----------------------------------
// Tool-owned convergence signal. Running the project's check command in the
// worktree (exit 0 = green) is the AUTHORITATIVE green check — a worker's own
// claim of success is never trusted (it could fake a pass).
//
// Run via a shell rather than a hardcoded `bash` argv, because `bash` is ENOENT
// on native Windows PowerShell — the old form launched bash with a -c argument
// and made every convergence check spuriously fail there. We pick the shell so the
// command runs on every platform AND keeps its original interpreter on POSIX:
// - win32: shell:true → cmd.exe (bash is unavailable; there is no other
// choice, and a Construction check command on Windows is written for it).
// - POSIX with /bin/bash present: shell:"/bin/bash" → preserves the exact
// bash interpreter the old code used, so a bash-only check command
// (`[[ ]]`, process substitution, arrays) keeps working. Bare shell:true
// would route through /bin/sh, which on dash-default distros (Debian/Ubuntu)
// would regress those bashisms — so we keep bash where it exists.
// - POSIX without /bin/bash: shell:true → /bin/sh (best available).
// Exit-code semantics (0 = converged) and the 60s timeout are unchanged across
// all three.
//
// checkCmd is shell-interpreted, so shell metacharacters in it are honoured —
// that is acceptable here: the swarm only fires under human-granted
// Construction autonomy inside a live session, and checkCmd is the user's own
// project check command (a trusted input), not attacker-controlled. (It was
// already shell-interpreted under the old `bash -c` form — no new surface.)
function checkConverged(cwd: string, checkCmd: string): boolean {
const shell =
process.platform !== "win32" && existsSync("/bin/bash")
? "/bin/bash"
: true;
const result = spawnSync(checkCmd, {
cwd,
encoding: "utf-8",
timeout: 60_000,
shell,
});
return result.status === 0;
}
// Anti-tamper, re-derived from the worktree's own git fork (stateless): the
// protected file's pristine bytes are its content at HEAD (the fork point), so a
// worker edit shows as a working-tree change. `git diff --quiet HEAD -- <path>`
// exits 0 when unchanged, 1 when changed; any other status (e.g. 128 — path not
// tracked at HEAD) is not a confirmed tamper, so only status 1 trips the guard.
function fileTampered(cwd: string, relPath: string): boolean {
const result = spawnSync("git", ["diff", "--quiet", "HEAD", "--", relPath], {
cwd,
encoding: "utf-8",
timeout: 60_000,
});
return result.status === 1;
}
interface Verdict {
exists: boolean;
converged: boolean;
tampered: boolean;
confineError?: string;
}
// Compute a unit's stateless verdict from on-disk state alone. Re-derives the
// worktree path from (projectDir, unit) — no stored handle — so check and
// finalize agree without sharing state.
function verdictFor(
unit: string,
projectDir: string,
checkCmd: string,
testFile?: string
): Verdict {
const wt = worktreePath(projectDir, swarmBoltSlug(unit));
if (!existsSync(wt)) {
return { exists: false, converged: false, tampered: false };
}
const converged = checkConverged(wt, checkCmd);
let tampered = false;
let confineError: string | undefined;
if (testFile) {
// Confine the path inside the unit's worktree — a `../` escape would point
// the guard at a file the worker never touched and silently DISABLE it, so
// reject it as a configuration error rather than ship a false "untampered".
const candidate = resolve(wt, testFile);
const root = resolve(wt) + sep;
if (!candidate.startsWith(root)) {
confineError = `--test-file resolves outside the unit worktree: ${testFile}`;
} else {
tampered = fileTampered(wt, testFile);
}
}
return { exists: true, converged, tampered, confineError };
}
interface ReviewerRequirement {
stage: string;
reviewer: string | null;
reviewClass: "adversarial" | "advisory";
maxIterations: number;
error?: string;
}
function reviewerRequirement(projectDir: string): ReviewerRequirement {
try {
const stage = getField(readStateFile(projectDir), "Current Stage")?.trim() ?? "";
if (!stage) {
return {
stage: "",
reviewer: null,
reviewClass: "adversarial",
maxIterations: 2,
error: "cannot resolve reviewer requirement: Current Stage is empty",
};
}
const definition = resolveStage(stage);
if (!definition) {
return {
stage,
reviewer: null,
reviewClass: "adversarial",
maxIterations: 2,
error: `cannot resolve reviewer requirement: stage "${stage}" is absent from the stage graph`,
};
}
const reviewClass = definition.review_class ?? "adversarial";
return {
stage,
reviewer: definition.reviewer?.trim() || null,
reviewClass,
maxIterations:
reviewClass === "advisory"
? 1
: definition.reviewer_max_iterations ?? 2,
};
} catch (e) {
return {
stage: "",
reviewer: null,
reviewClass: "adversarial",
maxIterations: 2,
error: `cannot resolve reviewer requirement: ${e instanceof Error ? e.message : String(e)}`,
};
}
}
// A claimed autonomous unit must prove its configured review happened inside
// this Bolt attempt. BOLT_STARTED is a stronger floor than STAGE_STARTED here:
// it excludes a matching receipt inherited from main when prepare forked the
// worktree, while preserving a receipt across a merge retry on that worktree.
function reviewerReceiptError(
projectDir: string,
unit: string,
stage: string,
reviewer: string,
reviewClass: "adversarial" | "advisory",
maxIterations: number,
): ReceiptCheck {
const boltSlug = swarmBoltSlug(unit);
const wt = worktreePath(projectDir, boltSlug);
const creationRows = readAuditShardEvents(projectDir)
.filter(
(row) =>
row.event === "WORKTREE_CREATED" &&
auditBlockField(row.block, "Bolt slug") === boltSlug &&
(
auditBlockField(row.block, "Worktree path") !== null &&
resolveAuditWorktreePath(
projectDir,
auditBlockField(row.block, "Worktree path") as string,
) === wt
),
)
.sort((a, b) => {
if (a.timestamp !== b.timestamp) return a.timestamp < b.timestamp ? -1 : 1;
if (a.shard === b.shard) return a.pos - b.pos;
return a.shard < b.shard ? -1 : 1;
});
const creationBlock = creationRows.at(-1)?.block ?? null;
const creationBaseCommit = creationBlock === null
? null
: auditBlockField(creationBlock, "Base commit");
const creationBaseListing = creationBlock === null
? null
: auditBlockField(creationBlock, "Base Source Listing");
const creationModern = creationBaseCommit !== null || creationBaseListing !== null;
const reviewAttempt = worktreeReviewAttemptProjection(
wt,
readAuditShardEvents(wt),
{
boltSlug,
unit,
stage,
reviewer,
reviewClass,
maxIterations,
},
);
if (reviewAttempt.boltStart === null) {
return {
error: `claimed converged but worktree audit has no BOLT_STARTED boundary for unit "${unit}"`,
};
}
const boltStartBlock = reviewAttempt.boltStart.block;
const baseCommit = auditBlockField(boltStartBlock, "Base commit");
const baseSourceListing = auditBlockField(boltStartBlock, "Base Source Listing");
if (
creationModern &&
(baseCommit !== creationBaseCommit || baseSourceListing !== creationBaseListing)
) {
return {
error: `claimed converged but modern WORKTREE_CREATED attestation was not propagated to BOLT_STARTED for unit "${unit}"`,
};
}
let verifiedBaseListing: Map<string, string> | null = null;
if (baseCommit !== null) {
const metaPath = join(wt, ".aidlc", "worktree-meta.json");
let meta: unknown;
try {
meta = JSON.parse(readFileSync(metaPath, "utf-8"));
} catch {
return { error: `claimed converged but worktree base-commit metadata is missing or malformed for unit "${unit}"` };
}
if (
typeof meta !== "object" || meta === null || Array.isArray(meta) ||
(meta as Record<string, unknown>).baseCommit !== baseCommit ||
baseSourceListing === null ||
(meta as Record<string, unknown>).baseSourceListing !== baseSourceListing
) {
return { error: `claimed converged but worktree Base commit/source listing does not match its BOLT_STARTED attestation for unit "${unit}"` };
}
const listingPath = join(wt, ".aidlc", "base-source-listing.tsv");
let serialized: string;
try {
serialized = readFileSync(listingPath, "utf-8");
} catch {
return { error: `claimed converged but worktree base source listing is missing for unit "${unit}"` };
}
if (`sha256:${sourceListingSha256(serialized)}` !== baseSourceListing) {
return { error: `claimed converged but worktree base source listing hash does not match for unit "${unit}"` };
}
verifiedBaseListing = parseSourceListing(serialized);
if (verifiedBaseListing === null) {
return { error: `claimed converged but worktree base source listing is malformed for unit "${unit}"` };
}
}
const latestTerminal =
reviewAttempt.terminal === null
? null
: {
block: reviewAttempt.terminal.event.block,
binding: reviewAttempt.terminal.binding,
};
if (latestTerminal === null) {
return {
error:
`claimed converged but no terminal REVIEW_COMPLETED for stage "${stage}", ` +
`unit "${unit}", reviewer "${reviewer}" exists after this Bolt started`,
};
}
const definition = resolveStage(stage);
const recordedArtifactFp = auditBlockField(latestTerminal.block, "Artifact Fingerprint");
const currentArtifactFp = definition
? reviewArtifactFingerprint(wt, definition, unit, {
requireRequiredArtifacts: true,
})
: null;
if (
recordedArtifactFp === null ||
!/^sha256:[0-9a-f]{64}$/.test(recordedArtifactFp) ||
currentArtifactFp === null ||
recordedArtifactFp !== currentArtifactFp
) {
return {
error:
`claimed converged but no terminal REVIEW_COMPLETED for stage "${stage}", ` +
`unit "${unit}", reviewer "${reviewer}" with a current artifact fingerprint exists after this Bolt started`,
};
}
if (!definition?.workspace_requires) {
return { error: null, artifactFingerprint: recordedArtifactFp };
}
const recordedSourceFp = auditBlockField(latestTerminal.block, "Source Fingerprint");
if (process.env.AIDLC_SKIP_SOURCE_FRESHNESS === "1") {
return { error: null, artifactFingerprint: recordedArtifactFp };
}
if (recordedSourceFp === null) {
if (baseCommit === null) {
return { error: null, artifactFingerprint: recordedArtifactFp };
}
return {
error:
`claimed converged but modern worktree unit "${unit}" has no Source Fingerprint; ` +
`re-run the reviewer in the worktree and record a fresh verdict before finalizing`,
};
}
const currentSourceFp = worktreeSourceFingerprint(wt);
if (
recordedSourceFp === UNBINDABLE_FINGERPRINT ||
currentSourceFp === null ||
currentSourceFp !== recordedSourceFp
) {
return {
error:
`claimed converged but the reviewed source no longer matches its worktree's ` +
`fingerprint for stage "${stage}", unit "${unit}" (source-fingerprint mismatch); ` +
`re-invoke the reviewer against the current worktree source and record a fresh ` +
`verdict before finalizing`,
};
}
// Pre-upgrade worktrees have no attested base commit and retain migration
// fail-open behavior. Modern worktrees must validate the exact unit binding
// that the reviewer saw before trusting its claims for footprint coverage.
let unitSourceFingerprint: string | undefined;
if (baseCommit !== null) {
const recordedUnitFp = auditBlockField(
latestTerminal.block,
"Unit Source Fingerprint",
);
const bindingBypass =
auditBlockField(latestTerminal.block, "Unit Source Binding Bypass") ===
"true";
if (bindingBypass || recordedUnitFp === null || recordedUnitFp === UNBINDABLE_FINGERPRINT) {
return {
error:
`claimed converged but unit "${unit}" has no verifiable modern Unit Source Fingerprint; ` +
`re-run the reviewer in the worktree and record a fresh verdict before finalizing`,
};
}
unitSourceFingerprint = recordedUnitFp;
const manifest = readUnitSourceManifest(wt, stage, unit, {
worktreeRelative: true,
});
const snapshot = readUnitSourceSnapshot(wt, stage, unit, recordedUnitFp);
if (
!manifest.ok ||
snapshot === null ||
snapshot.manifestSha256 !== manifest.rawBytesSha256
) {
return {
error:
`claimed converged but unit "${unit}"'s reviewed source manifest binding is missing, ` +
`corrupt, or no longer matches its review; re-run the reviewer in the worktree and ` +
`record a fresh verdict before finalizing`,
};
}
const reviewedClaims: SourceClaimModel = {
claims: manifest.claims,
prefixes: manifest.prefixes,
};
const idx = join(tmpdir(), `aidlc-swarm-footprint-${process.pid}-${randomUUID().slice(0, 8)}`);
const env = { ...process.env, GIT_INDEX_FILE: idx };
const git = (args: string[]) => spawnSync("git", ["-C", wt, ...args], {
env,
encoding: "utf-8",
maxBuffer: 512 * 1024 * 1024,
});
try {
if (git(["read-tree", "HEAD"]).status !== 0 || git(["add", "-A"]).status !== 0) {
return { error: `claimed converged but the worktree footprint could not be computed for unit "${unit}"` };
}
if (shapeSourceSnapshotIndex(wt, idx, true) === null) {
return {
error:
`claimed converged but the reviewed source boundary could not be applied to unit "${unit}"'s footprint`,
};
}
const tree = git(["write-tree"]);
if (tree.status !== 0 || !tree.stdout.trim()) return { error: `claimed converged but the worktree footprint tree could not be written for unit "${unit}"` };
const diff = git([
"diff",
"--name-only",
"-z",
"--no-renames",
baseCommit,
tree.stdout.trim(),
]);
if (diff.status !== 0) return { error: `claimed converged but the worktree footprint could not be compared for unit "${unit}"` };
const outside = new Set(
diff.stdout
.split("\0")
.filter(Boolean),
);
const currentListing = workspaceSourceListing(wt);
if (verifiedBaseListing === null || currentListing === null) {
return { error: `claimed converged but raw-aware worktree footprint evidence is unavailable for unit "${unit}"` };
}
for (const [path, oid] of verifiedBaseListing) {
if (!sourceListingEntriesEqual(currentListing.get(path), oid)) {
outside.add(path.slice(path.indexOf("\0") + 1));
}
}
for (const path of currentListing.keys()) {
if (!verifiedBaseListing.has(path)) outside.add(path.slice(path.indexOf("\0") + 1));
}
const outsideClaims = [...outside]
.filter((path) => !sourceClaimCovers(`\0${path}`, reviewedClaims));
if (outsideClaims.length > 0) {
const rendered = outsideClaims.slice(0, 10).join(", ") +
(outsideClaims.length > 10 ? ` … and ${outsideClaims.length - 10} more` : "");
return {
error:
`claimed converged but the worktree wrote application-source paths outside unit "${unit}"'s ` +
`source manifest (${rendered}); update construction/${unit}/code-generation/source-manifest.json ` +
`in the worktree, re-run the reviewer there, and record a fresh verdict before finalizing`,
};
}
} finally {
rmSync(idx, { force: true });
}
}
return {
error: null,
artifactFingerprint: recordedArtifactFp,
sourceFingerprint: recordedSourceFp,
unitSourceFingerprint,
};
}
function captureReviewedRecordSnapshot(
projectDir: string,
unit: string,
stage: NonNullable<ReturnType<typeof resolveStage>>,
receipt: ReceiptCheck,
): { snapshot?: ReviewedRecordSnapshot; error?: string } {
const wt = worktreePath(projectDir, swarmBoltSlug(unit));
const artifacts = reviewArtifactBytesSnapshot(wt, stage, unit, {
requireRequiredArtifacts: true,
captureBytes: true,
});
if (artifacts === null) {
return { error: `cannot snapshot required record artifacts for unit "${unit}"` };
}
if (
receipt.artifactFingerprint !== undefined &&
artifacts.fingerprint !== receipt.artifactFingerprint
) {
return {
error:
`record artifacts changed while finalizing unit "${unit}"; ` +
`re-run the reviewer against the current artifacts`,
};
}
const entries: ReviewedRecordSnapshotEntry[] = [];
for (const artifact of artifacts.entries) {
if (artifact.state === "not-file") {
return {
error:
`record artifact ${artifact.logicalPath} for unit "${unit}" is not a regular file`,
};
}
if (artifact.state === "file" && artifact.bytes === undefined) {
return {
error: `cannot capture record artifact ${artifact.logicalPath} for unit "${unit}"`,
};
}
entries.push({
logicalPath: artifact.logicalPath,
bytes: artifact.state === "file" ? artifact.bytes! : null,
});
}
if (receipt.unitSourceFingerprint !== undefined) {
const wtRecord = recordDir(wt);
if (wtRecord === null) {
return { error: `cannot resolve reviewed source evidence for unit "${unit}"` };
}
const manifest = readUnitSourceManifest(wt, stage.slug, unit, {
worktreeRelative: true,
});
const snapshot = readUnitSourceSnapshot(
wt,
stage.slug,
unit,
receipt.unitSourceFingerprint,
);
if (
!manifest.ok ||
snapshot === null ||
snapshot.manifestSha256 !== manifest.rawBytesSha256
) {
return {
error:
`reviewed source evidence changed while finalizing unit "${unit}"; ` +
`re-run the reviewer`,
};
}
const manifestPath = join(
wtRecord,
"construction",
unit,
stage.slug,
"source-manifest.json",
);
let manifestBytes: Buffer;
try {
manifestBytes = readRegularFileNoFollowOrThrow(
assertNoSymlinkInChainOrThrow(
realpathSync(wt),
relative(wt, manifestPath),
),
`source manifest for unit ${unit}`,
);
} catch {
return { error: `cannot capture reviewed source evidence for unit "${unit}"` };
}
if (
createHash("sha256").update(manifestBytes).digest("hex") !==
manifest.rawBytesSha256
) {
return {
error:
`reviewed source evidence changed while finalizing unit "${unit}"; ` +
`re-run the reviewer`,
};
}
entries.push(
{
logicalPath:
`construction/${unit}/${stage.slug}/source-manifest.json`,
bytes: manifestBytes,
},
);
}
return { snapshot: { entries } };
}
function mergeReviewedRecordSnapshot(
projectDir: string,
unit: string,
snapshot: ReviewedRecordSnapshot,
): string | null {
const record = recordDir(projectDir);
if (record === null) return `cannot resolve the main record directory for unit "${unit}"`;
let root: string;
try {
root = assertNoSymlinkInChainOrThrow(
realpathSync(projectDir),
relative(projectDir, record),
);
if (!lstatSync(root).isDirectory()) {
return `main record path is not a directory for unit "${unit}"`;
}
} catch (error) {
return (
`cannot validate the main record directory for unit "${unit}": ` +
`${error instanceof Error ? error.message : String(error)}`
);
}
const operations: Array<{
target: string;
logicalPath: string;
next: Buffer | null;
previous: Buffer | null;
}> = [];
for (const entry of snapshot.entries) {
try {
const target = assertNoSymlinkInChainOrThrow(root, entry.logicalPath);
const previous = existsSync(target)
? readRegularFileNoFollowOrThrow(
target,
`existing record artifact ${entry.logicalPath}`,
)
: null;
operations.push({
target,
logicalPath: entry.logicalPath,
next: entry.bytes,
previous,
});
} catch (error) {
return (
`record artifact preflight failed for ${entry.logicalPath}: ` +
`${error instanceof Error ? error.message : String(error)}`
);
}
}
const applied: typeof operations = [];
try {
for (const operation of operations) {
if (operation.next === null) {
rmSync(operation.target, { force: true });
applied.push(operation);
} else {
mkdirSync(dirname(operation.target), { recursive: true });
writeBufferAtomic(operation.target, operation.next);
applied.push(operation);
if (
process.env.AIDLC_TEST === "1" &&
process.env.AIDLC_TEST_RECORD_VERIFY_FAIL === operation.logicalPath
) {
throw new Error(
`injected verification failure for ${operation.logicalPath}`,
);
}
if (
!readRegularFileNoFollowOrThrow(
operation.target,
`landed record artifact ${operation.logicalPath}`,
).equals(operation.next)
) {
throw new Error(`verification failed for ${operation.logicalPath}`);
}
}
}
} catch (error) {
const rollbackErrors: string[] = [];
for (const operation of [...applied].reverse()) {
try {
if (operation.previous === null) {
rmSync(operation.target, { force: true });
} else {
mkdirSync(dirname(operation.target), { recursive: true });
writeBufferAtomic(operation.target, operation.previous);
}
} catch (rollbackError) {
rollbackErrors.push(
`${operation.logicalPath}: ${
rollbackError instanceof Error
? rollbackError.message
: String(rollbackError)
}`,
);
}
}
return (
`record artifact transaction failed for unit "${unit}": ` +
`${error instanceof Error ? error.message : String(error)}` +
(rollbackErrors.length > 0
? `; rollback failed for ${rollbackErrors.join(", ")}`
: "")
);
}
return null;
}
// Materialize the reviewed application bytes as an immutable commit without
// moving the Bolt branch. The temporary index starts from HEAD, overlays the
// worktree, then restores framework-owned paths from HEAD so the later source
// merge carries application source only. Recompute the fingerprint after the
// object is written to close a concurrent-edit window; the validated value is
// the one carried to the convergence row.
function recoverableSubmoduleUrls(
repoDir: string,
): Map<string, string> | null {
const modulesPath = join(repoDir, ".gitmodules");
if (!existsSync(modulesPath)) return new Map();
const paths = spawnSync(
"git",
[
"-C",
repoDir,
"config",
"-f",
".gitmodules",
"--get-regexp",
"^submodule\\..*\\.path$",
],
{ encoding: "utf-8", maxBuffer: 512 * 1024 * 1024 },
);
if (paths.status === 1) return new Map();
if (paths.status !== 0) return null;
const recoverable = new Map<string, string>();
for (const line of paths.stdout.split(/\r?\n/)) {
if (!line) continue;
const separator = line.indexOf(" ");
if (separator <= 0) return null;
const key = line.slice(0, separator);
const path = line.slice(separator + 1).trim().replace(/\\/g, "/");
if (!key.endsWith(".path") || !path) return null;
const urlKey = `${key.slice(0, -".path".length)}.url`;
const url = spawnSync(
"git",
["-C", repoDir, "config", "-f", ".gitmodules", "--get", urlKey],
{ encoding: "utf-8", maxBuffer: 512 * 1024 * 1024 },
);
if (url.status !== 0 || !url.stdout.trim()) continue;
recoverable.set(path, url.stdout.trim());
}
return recoverable;
}
function configuredParentRemoteUrl(repoDir: string): string | null {
const branch = spawnSync(
"git",
["-C", repoDir, "symbolic-ref", "--quiet", "--short", "HEAD"],
{ encoding: "utf-8", maxBuffer: 512 * 1024 * 1024 },
);
if (branch.status === 0 && branch.stdout.trim()) {
const remoteName = spawnSync(
"git",
[
"-C",
repoDir,
"config",
"--get",
`branch.${branch.stdout.trim()}.remote`,
],
{ encoding: "utf-8", maxBuffer: 512 * 1024 * 1024 },
);
if (
remoteName.status === 0 &&
remoteName.stdout.trim() &&
remoteName.stdout.trim() !== "."
) {
const remoteUrl = spawnSync(
"git",
[
"-C",
repoDir,
"config",
"--get",
`remote.${remoteName.stdout.trim()}.url`,
],
{ encoding: "utf-8", maxBuffer: 512 * 1024 * 1024 },
);
if (remoteUrl.status === 0 && remoteUrl.stdout.trim()) {
return remoteUrl.stdout.trim();
}
}
}
const origin = spawnSync(
"git",
["-C", repoDir, "config", "--get", "remote.origin.url"],
{ encoding: "utf-8", maxBuffer: 512 * 1024 * 1024 },
);
return origin.status === 0 && origin.stdout.trim()
? origin.stdout.trim()
: null;
}
function resolveRelativeSubmoduleUrl(
repoDir: string,
metadataUrl: string,
): string | null {
if (!metadataUrl.startsWith("./") && !metadataUrl.startsWith("../")) {
return metadataUrl;
}
const parentUrl = configuredParentRemoteUrl(repoDir);
if (!parentUrl) return null;
if (/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(parentUrl)) {
try {
const base = parentUrl.endsWith("/") ? parentUrl : `${parentUrl}/`;
return new URL(metadataUrl, base).toString();
} catch {
return null;
}
}
if (
!/^[A-Za-z]:[\\/]/.test(parentUrl) &&
/^[^/\\:]+:.+/.test(parentUrl)
) {
const colon = parentUrl.indexOf(":");
const host = parentUrl.slice(0, colon);
const remotePath = parentUrl.slice(colon + 1);
return `${host}:${posix.normalize(`${remotePath}/${metadataUrl}`)}`;
}
return resolve(parentUrl, metadataUrl);
}
const NEW_GITLINK_RECOVERY_BUDGET_MS = 30_000;
const NEW_GITLINK_RECOVERY_COMMAND_TIMEOUT_MS = 15_000;
const NEW_GITLINK_RECOVERY_PROOF_CAP = 32;
interface NewGitlinkRecoveryBudget {
budgetMs: number;
commandTimeoutMs: number;
deadlineMs: number | null;
proofCap: number;
proofsStarted: number;
}
function positiveIntegerEnv(name: string, fallback: number): number {
const value = process.env[name];
return value && /^[1-9][0-9]*$/.test(value) ? Number(value) : fallback;
}
function newGitlinkRecoveryBudget(): NewGitlinkRecoveryBudget {
return {
budgetMs: positiveIntegerEnv(
"AIDLC_TEST_NEW_GITLINK_RECOVERY_BUDGET_MS",
NEW_GITLINK_RECOVERY_BUDGET_MS,
),
commandTimeoutMs: positiveIntegerEnv(
"AIDLC_TEST_NEW_GITLINK_RECOVERY_COMMAND_TIMEOUT_MS",
NEW_GITLINK_RECOVERY_COMMAND_TIMEOUT_MS,
),
deadlineMs: null,
proofCap: positiveIntegerEnv(
"AIDLC_TEST_NEW_GITLINK_RECOVERY_PROOF_CAP",
NEW_GITLINK_RECOVERY_PROOF_CAP,
),
proofsStarted: 0,
};
}
function remainingNewGitlinkRecoveryMs(
budget: NewGitlinkRecoveryBudget,
): number | null {
if (budget.deadlineMs === null) {
budget.deadlineMs = Date.now() + budget.budgetMs;
}
const remaining = budget.deadlineMs - Date.now();
return remaining <= 0
? null
: Math.min(budget.commandTimeoutMs, remaining);
}
function newGitlinkRecoveryError(
repoDir: string,
subDir: string,
path: string,
metadataUrl: string,
commit: string,
budget: NewGitlinkRecoveryBudget,
): string | null {
if (budget.proofsStarted >= budget.proofCap) {
return `new submodule recovery proof cap exceeded (${budget.proofCap} per finalize)`;
}
const lsRemoteTimeout = remainingNewGitlinkRecoveryMs(budget);
if (lsRemoteTimeout === null) {
return `new submodule recovery deadline exceeded (${budget.budgetMs}ms cumulative per finalize)`;
}
budget.proofsStarted += 1;
const endpoint = resolveRelativeSubmoduleUrl(repoDir, metadataUrl);
if (!endpoint) {
return `cannot resolve .gitmodules recovery URL for new submodule ${path}`;
}
if (metadataUrl.startsWith("./") || metadataUrl.startsWith("../")) {
const origin = spawnSync(
"git",
["-C", subDir, "remote", "get-url", "origin"],
{ encoding: "utf-8", maxBuffer: 512 * 1024 * 1024 },
);
const normalize = (value: string): string =>
value.trim().replace(/\\/g, "/").replace(/\/+$/, "");
if (
origin.status !== 0 ||
normalize(origin.stdout) !== normalize(endpoint)
) {
return `new submodule ${path} origin does not match its resolved .gitmodules recovery URL`;
}
}
const advertised = spawnSync(
"git",
["ls-remote", endpoint, "HEAD", "refs/heads/*", "refs/tags/*"],
{
encoding: "utf-8",
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
maxBuffer: 512 * 1024 * 1024,
timeout: lsRemoteTimeout,
},
);
if (advertised.status !== 0) {
if (
budget.deadlineMs !== null &&
Date.now() >= budget.deadlineMs
) {
return `new submodule recovery deadline exceeded (${budget.budgetMs}ms cumulative per finalize)`;
}
return `new submodule ${path} recovery endpoint is unavailable`;
}
const advertisedRefs = new Set<string>();
for (const line of advertised.stdout.split(/\r?\n/)) {
if (!line) continue;
const [oid, ref] = line.split(/\s+/, 2);
if (!/^[0-9a-f]{40,64}$/.test(oid) || !ref) continue;
const baseRef = ref.endsWith("^{}") ? ref.slice(0, -3) : ref;
if (
baseRef !== "HEAD" &&
!baseRef.startsWith("refs/heads/") &&
!baseRef.startsWith("refs/tags/")
) {
continue;
}
if (!ref.endsWith("^{}")) advertisedRefs.add(ref);
}
if (advertisedRefs.size === 0) {
return `new submodule ${path} recovery endpoint advertises no cloneable refs`;
}
if (advertisedRefs.size > 10_000) {
return `new submodule ${path} recovery endpoint advertises too many refs`;
}
const recoveryRefspecs = [...advertisedRefs].sort().map((ref) => {
if (ref === "HEAD") return "+HEAD:refs/aidlc/recovery/HEAD";
if (ref.startsWith("refs/heads/")) {
return `+${ref}:refs/aidlc/recovery/heads/${ref.slice("refs/heads/".length)}`;
}
return `+${ref}:refs/aidlc/recovery/tags/${ref.slice("refs/tags/".length)}`;
});
const recoveryRefspecInput = `${recoveryRefspecs.join("\n")}\n`;
if (Buffer.byteLength(recoveryRefspecInput, "utf-8") > 1024 * 1024) {
return `new submodule ${path} recovery endpoint refspecs exceed the size budget`;
}
const recoveryRepo = mkdtempSync(
join(tmpdir(), `aidlc-submodule-recovery-${process.pid}-`),
);
try {
const initialized = spawnSync(
"git",
["-C", recoveryRepo, "init", "--bare", "-q"],
{ encoding: "utf-8", maxBuffer: 512 * 1024 * 1024 },
);
if (initialized.status !== 0) {
return `cannot initialize recovery proof for new submodule ${path}`;
}
const fetchTimeout = remainingNewGitlinkRecoveryMs(budget);
if (fetchTimeout === null) {
return `new submodule recovery deadline exceeded (${budget.budgetMs}ms cumulative per finalize)`;
}
const fetched = spawnSync(
"git",
[
"-C",
recoveryRepo,
"fetch",
"--quiet",
"--no-tags",
"--no-write-fetch-head",
"--filter=blob:none",
"--stdin",
endpoint,
],
{
encoding: "utf-8",
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
input: recoveryRefspecInput,
maxBuffer: 512 * 1024 * 1024,
timeout: fetchTimeout,
},
);
if (fetched.status !== 0) {
if (
budget.deadlineMs !== null &&
Date.now() >= budget.deadlineMs
) {
return `new submodule recovery deadline exceeded (${budget.budgetMs}ms cumulative per finalize)`;
}
return `cannot fetch advertised recovery history for new submodule ${path}`;
}
const recovered = spawnSync(
"git",
["-C", recoveryRepo, "cat-file", "-e", `${commit}^{commit}`],
{ encoding: "utf-8", maxBuffer: 512 * 1024 * 1024 },
);
if (recovered.status === 0) return null;
} finally {
rmSync(recoveryRepo, { recursive: true, force: true });
}
return `new submodule ${path} commit ${commit} is not reachable from an advertised recovery ref`;
}
function initializedSubmoduleSourceError(
subDir: string,
displayPath: string,
visited: Set<string>,
depth = 1,
): string | null {
if (depth > 64) {
return `cannot verify initialized submodule ${displayPath}: nesting exceeds 64 levels`;
}
let real: string;
try {
real = realpathSync(subDir);
} catch {
return `cannot resolve initialized submodule ${displayPath}`;
}
if (visited.has(real)) return null;
visited.add(real);
if (visited.size > 10_000) {
return "cannot verify initialized submodules: more than 10000 checkouts are materialized";
}
const status = spawnSync(
"git",
[
"-C",
subDir,
"status",
"--porcelain=v1",
"-z",
"--untracked-files=all",
"--ignore-submodules=none",
],
{ encoding: "utf-8", maxBuffer: 512 * 1024 * 1024 },
);
if (status.status !== 0) {
return `cannot verify reviewed submodule state for ${displayPath}`;
}
if (status.stdout.length > 0) {
return (
`cannot bind dirty initialized submodule ${displayPath}; commit or discard its reviewed ` +
"changes, then re-run the reviewer before finalizing"
);
}
const sourcePaths = workspaceSourceSnapshotPaths(subDir, false);
if (sourcePaths === null) {
return `cannot resolve the reviewed source boundary for initialized submodule ${displayPath}`;
}
if (sourcePaths.length > 0) {
const ignored = spawnSync(
"git",
["-C", subDir, "check-ignore", "-z", "--stdin"],
{
input: `${sourcePaths.join("\0")}\0`,
encoding: "utf-8",
maxBuffer: 512 * 1024 * 1024,
},
);
if (ignored.status !== 0 && ignored.status !== 1) {
return `cannot verify ignored reviewed source for initialized submodule ${displayPath}`;
}
if (ignored.status === 0 && ignored.stdout.length > 0) {
return (
`cannot bind dirty initialized submodule ${displayPath}; ignored application source ` +
"is part of the reviewed fingerprint but cannot be represented by the parent gitlink"
);
}
}
const gitlinks = spawnSync(
"git",
["-C", subDir, "ls-files", "-s", "-z"],
{ encoding: "utf-8", maxBuffer: 512 * 1024 * 1024 },
);
if (gitlinks.status !== 0) {
return `cannot enumerate nested submodules for ${displayPath}`;
}
const trackedNestedPaths = new Set<string>();
for (const record of gitlinks.stdout.split("\0")) {
if (!record.startsWith("160000 ")) continue;
const tab = record.indexOf("\t");
if (tab === -1) {
return `cannot parse a nested submodule gitlink under ${displayPath}`;
}
const nestedPath = record.slice(tab + 1);
trackedNestedPaths.add(nestedPath.replace(/\\/g, "/"));
const nestedDir = join(subDir, nestedPath);
if (!existsSync(join(nestedDir, ".git"))) continue;
const nestedError = initializedSubmoduleSourceError(
nestedDir,
`${displayPath}/${nestedPath.replace(/\\/g, "/")}`,
visited,
depth + 1,
);
if (nestedError) return nestedError;
}
const embeddedPaths = workspaceSourceEmbeddedGitPaths(subDir, false);
if (embeddedPaths === null) {
return `cannot resolve embedded Git checkouts under initialized submodule ${displayPath}`;
}
for (const embeddedPath of embeddedPaths) {
if (trackedNestedPaths.has(embeddedPath)) continue;
const embeddedDir = join(subDir, embeddedPath);
if (!existsSync(join(embeddedDir, ".git"))) continue;
const embeddedDisplayPath = `${displayPath}/${embeddedPath}`;
const embeddedError = initializedSubmoduleSourceError(
embeddedDir,
embeddedDisplayPath,
visited,
depth + 1,
);
if (embeddedError) return embeddedError;
return (
`cannot bind embedded Git checkout ${embeddedDisplayPath}: it is not a tracked submodule. ` +
"Use git submodule add so the parent records a gitlink and .gitmodules recovery metadata, " +
"or flatten/remove the embedded checkout before re-running review."
);
}
return null;
}
function bindReviewedSource(
projectDir: string,
unit: string,
fingerprint: string,
recoveryBudget: NewGitlinkRecoveryBudget,
): { binding?: SourceBinding; error?: string } {
const wt = worktreePath(projectDir, unit);
const idx = join(tmpdir(), `aidlc-swarm-source-${process.pid}-${randomUUID().slice(0, 8)}`);
// commit-tree is an internal snapshot operation, not a user-authored commit.
// Give it a framework-owned identity so finalize does not depend on ambient
// user.name/user.email configuration (CI and fresh automation often have none).
const env = {
...process.env,
GIT_INDEX_FILE: idx,
GIT_AUTHOR_NAME: "AI-DLC",
GIT_AUTHOR_EMAIL: "aidlc@localhost",
GIT_COMMITTER_NAME: "AI-DLC",
GIT_COMMITTER_EMAIL: "aidlc@localhost",
};
const git = (args: string[]) => spawnSync("git", ["-C", wt, ...args], {
env,
encoding: "utf-8",
maxBuffer: 512 * 1024 * 1024,
});
try {
const head = git(["rev-parse", "HEAD^{commit}"]);
if (head.status !== 0 || !head.stdout.trim()) return { error: "cannot resolve the Bolt HEAD commit" };
if (git(["read-tree", "HEAD"]).status !== 0) return { error: "cannot seed the source snapshot index" };
const initialSubmodules = git(["ls-files", "-s", "-z"]);
if (initialSubmodules.status !== 0) {
return { error: "cannot enumerate pre-shape submodule state" };
}
const initialGitlinkPaths = new Set<string>();
for (const record of initialSubmodules.stdout.split("\0")) {
if (!record.startsWith("160000 ")) continue;
const tab = record.indexOf("\t");
if (tab === -1) {
return { error: "cannot parse a pre-shape submodule gitlink" };
}
initialGitlinkPaths.add(record.slice(tab + 1).replace(/\\/g, "/"));
}
if (git(["add", "-A"]).status !== 0) return { error: "cannot stage the reviewed source snapshot" };
const shape = shapeSourceSnapshotIndex(wt, idx, true);
if (shape === null) {
return { error: "cannot apply the reviewed source boundary to the snapshot" };
}
if (shape.externalSymlinkPaths.length > 0) {
const rendered = shape.externalSymlinkPaths.slice(0, 10).join(", ") +
(
shape.externalSymlinkPaths.length > 10
? ` ... and ${shape.externalSymlinkPaths.length - 10} more`
: ""
);
return {
error:
`cannot bind external source symlink target${shape.externalSymlinkPaths.length === 1 ? "" : "s"} ` +
`(${rendered}); a Source Commit records link text but cannot represent external target bytes. ` +
"Move the target into the worktree or replace the link before re-running review.",
};
}
const modulesIndexed = git([
"ls-files",
"--error-unmatch",
"--",
".gitmodules",
]);
let recoverableNewGitlinks = new Map<string, string>();
if (modulesIndexed.status === 0) {
const recoverable = recoverableSubmoduleUrls(wt);
if (recoverable === null) {
return { error: "cannot parse .gitmodules recovery metadata" };
}
recoverableNewGitlinks = recoverable;
} else if (modulesIndexed.status !== 1) {
return { error: "cannot verify .gitmodules snapshot state" };
}
// The parent tree can represent only a submodule's checked-out commit
// (mode 160000), never dirty bytes inside that checkout. The fingerprint
// deliberately includes those bytes, so accepting them here would produce
// a Source Commit different from what the reviewer inspected. Fail closed
// rather than silently retaining the old gitlink. A clean submodule checked
// out at another commit remains representable: `git add -A` staged its new
// gitlink above.
const submodules = git(["ls-files", "-s", "-z"]);
if (submodules.status !== 0) return { error: "cannot verify reviewed submodule state" };
const visitedSubmodules = new Set<string>();
for (const record of submodules.stdout.split("\0")) {
if (!record.startsWith("160000 ")) continue;
const tab = record.indexOf("\t");
if (tab === -1) return { error: "cannot parse a reviewed submodule gitlink" };
const commit = record.slice(0, tab).split(" ")[1] ?? "";
if (!/^[0-9a-f]{40,64}$/.test(commit)) {
return { error: "cannot parse a reviewed submodule commit" };
}
const path = record.slice(tab + 1);
const subDir = join(wt, path);
if (!existsSync(join(subDir, ".git"))) continue; // uninitialized: no reviewed bytes to carry
const submoduleError = initializedSubmoduleSourceError(
subDir,
path.replace(/\\/g, "/"),
visitedSubmodules,
);
if (submoduleError) return { error: submoduleError };
const normalizedPath = path.replace(/\\/g, "/");
if (!initialGitlinkPaths.has(normalizedPath)) {
const recoveryUrl = recoverableNewGitlinks.get(normalizedPath);
if (recoveryUrl) {
const recoveryError = newGitlinkRecoveryError(
wt,
subDir,
normalizedPath,
recoveryUrl,
commit,
recoveryBudget,
);
if (recoveryError) return { error: recoveryError };
continue;
}
return {
error:
`cannot bind embedded Git checkout ${normalizedPath}: it is not a tracked submodule. ` +
"Use git submodule add so the parent records a gitlink and .gitmodules recovery metadata, " +
"or flatten/remove the embedded checkout before re-running review.",
};
}
}
const rawEntries = filteredRawIndexEntries(
wt,
idx,
shape.includedRegularPaths,
);
if (rawEntries === null) return { error: "cannot bind raw bytes for filtered source paths" };
for (const entry of rawEntries) {
const indexed = git(["ls-files", "-s", "-z", "--", entry.path]);
const mode = indexed.status === 0 ? indexed.stdout.slice(0, indexed.stdout.indexOf(" ")) : "";
if (!/^100(?:644|755)$/.test(mode)) {
return { error: `cannot resolve the index mode for filtered path ${entry.path}` };
}
const raw = git(["hash-object", "-w", "--no-filters", "--", entry.path]);
if (raw.status !== 0 || raw.stdout.trim() !== entry.sha) {
return { error: `cannot materialize raw reviewed bytes for filtered path ${entry.path}` };
}
if (git(["update-index", "--cacheinfo", mode, entry.sha, entry.path]).status !== 0) {
return { error: `cannot bind raw reviewed bytes for filtered path ${entry.path}` };
}
}
const tree = git(["write-tree"]);
if (tree.status !== 0 || !tree.stdout.trim()) return { error: "cannot write the reviewed source tree" };
const commit = git(["commit-tree", tree.stdout.trim(), "-p", head.stdout.trim(), "-m", `Reviewed source for Bolt ${unit}`]);
if (commit.status !== 0 || !commit.stdout.trim()) return { error: "cannot create the immutable reviewed-source commit" };
const after = worktreeSourceFingerprint(wt);
if (after === null || after !== fingerprint) {
return { error: "source-fingerprint mismatch while binding the reviewed source; re-run the reviewer" };
}
const commitSha = commit.stdout.trim();
const retained = git(["update-ref", reviewedSourceRef(unit, commitSha), commitSha]);
if (retained.status !== 0) {
return { error: "cannot retain the immutable reviewed-source commit" };
}
return { binding: { fingerprint, commit: commitSha } };
} finally {
rmSync(idx, { force: true });
}
}
// --- Audit emission (this tool owns the whole swarm taxonomy) ---------------
//
// The engine is read-only and the conductor (prose) never emits audit events, so
// the deterministic tool is the sole emitter. SWARM_STARTED fires once per batch
// in `prepare`; SWARM_DEGRADED fires there too when the conductor reports a loud
// downgrade. The per-unit pair, the per-failed-unit baton row, and the batch
// tally all fire from `finalize`, the authoritative gate.
function emitSwarmStarted(
pd: string,
batch: string,
units: string[],
obligations: string[],
concurrency: string,
attempt: SwarmAttemptStamp,
): void {
appendAuditEntry(
"SWARM_STARTED",
{
"Batch number": batch,
"Unit names": units.join(","),
"Unit obligations": obligations.join(","),
"Concurrency cap": concurrency,
Stage: attempt.stage,
"Run floor": attempt.floor,
},
pd
);
}
// Loud-degrade: AIDLC_USE_SWARM=1 was requested but the Workflow tool was
// unavailable, so the conductor ran the subagent floor. The referee makes the
// substrate difference invisible to convergence, but the downgrade is recorded.
function emitSwarmDegraded(pd: string, batch: string, requested: DriverName): void {
appendAuditEntry(
"SWARM_DEGRADED",
{
"Batch number": batch,
"Requested driver": requested,
"Fallback driver": "subagent",
},
pd
);
}
// Each converged row carries the exact attempt stamp captured by prepare.
// Finalize must never recompute this from current state: a late retry against a
// preserved prior-attempt worktree would otherwise be mislabeled as current.
function emitUnitConverged(
pd: string,
batch: string,
unit: string,
attempt: SwarmAttemptStamp,
binding?: SourceBinding,
sourceFreshnessBypassed = false,
): void {
appendAuditEntry(
"SWARM_UNIT_CONVERGED",
{
"Batch number": batch,
"Unit name": unit,
Stage: attempt.stage,
"Run floor": attempt.floor,
...(binding
? {
"Source Fingerprint": binding.fingerprint,
"Source Commit": binding.commit,
}
: sourceFreshnessBypassed
? { "Source Freshness Bypass": "true" }
: {}),
},
pd
);
}
function emitUnitFailed(
pd: string,
batch: string,
unit: string,
reason: FailureReason
): void {
appendAuditEntry(
"SWARM_UNIT_FAILED",
{ "Batch number": batch, "Unit name": unit, Reason: reason },
pd
);
}
function emitBatonReturned(
pd: string,
batch: string,
unit: string,
reason: FailureReason
): void {
appendAuditEntry(
"SWARM_BATON_RETURNED",
{ "Batch number": batch, "Unit name": unit, Reason: reason },
pd
);
}
function emitSwarmCompleted(
pd: string,
batch: string,
convergedCount: number,
failedCount: number
): void {
appendAuditEntry(
"SWARM_COMPLETED",
{
"Batch number": batch,
"Converged count": String(convergedCount),
"Failed count": String(failedCount),
},
pd
);
}
// Close a failed unit's per-Bolt lifecycle by composing `aidlc-bolt fail` (emits
// BOLT_FAILED paired with the BOLT_STARTED that `start --worktree` emitted).
// Preserves the worktree per the halt-and-ask contract. Best-effort: the swarm's
// own SWARM_UNIT_FAILED is the authoritative swarm signal, so a failure to emit
// BOLT_FAILED must not mask it.
function emitBoltFailed(pd: string, unit: string, errorSummary: string): void {
runTool(
"aidlc-bolt.ts",
["fail", "--name", unit, "--slug", swarmBoltSlug(unit), "--error", errorSummary],
pd
);
}
// --- prepare ----------------------------------------------------------------
function handlePrepare(rest: string[]): void {
const { flags } = parseArgs(rest);
const projectDir = resolveProjectDir(flags["project-dir"]);
if (!flags.batch || !/^[1-9][0-9]*$/.test(flags.batch)) {
fail("prepare requires --batch <positive integer>");
}
if (!flags.units) {
fail("prepare requires --units <comma-separated unit names>");
}
const units = splitCsv(flags.units);
if (units.length === 0) {
fail("--units resolved to an empty list");
}
if (flags["degraded-from"]) {
const requested = flags["degraded-from"] as DriverName;
if (!DRIVER_VALUES.includes(requested)) {
fail(`--degraded-from must be one of: ${DRIVER_VALUES.join(", ")}`);
}
}
const state = readStateFile(projectDir);
const stage = (getField(state, "Current Stage") ?? "")
.trim()
.toLowerCase()
.replace(/\s+/g, "-");
const autonomy = (getField(state, "Construction Autonomy Mode") ?? "").trim();
// Human lines for source drift accepted under Change Control `relaxed` when
// protected Code Generation authority started for the batch's units.
const swarmChangeNotices: string[] = [];
if (stage === "code-generation" && autonomy === "autonomous") {
const invalid = units
.map((unit) => evaluateCodeGenerationApproval(projectDir, { unit }))
.filter((approval) => !approval.ok);
if (invalid.length > 0) {
fail(
"prepare requires a current, explicitly approved Code Generation plan for every autonomous " +
`unit before worktrees are forked: ${invalid
.map((approval) => `${approval.unit} (${approval.reason})`)
.join("; ")}`,
);
}
try {
for (const unit of units) {
swarmChangeNotices.push(...beginCodeGeneration(projectDir, { unit }));
}
} catch (error) {
fail(
`prepare could not start protected Code Generation authority: ${
error instanceof Error ? error.message : String(error)
}`,
);
}
}
const dag = resolveBoltDag(projectDir, flags.intent, flags.space);
if (dag.state === "malformed") {
fail(
`prepare cannot resolve the authoritative unit DAG: ${dag.reason} ` +
`(${dag.detail}). Fix unit-of-work-dependency.md before starting the swarm.`,
);
}
const stageDefinition = resolveStage(stage);
if (dag.state !== "ok") {
fail("prepare requires a current resolved Unit DAG");
}
for (const unit of units) {
if (!dag.units.includes(unit)) {
fail(`prepare unit "${unit}" is not in the current resolved Unit DAG`);
}
if (
stageDefinition &&
filterProducesByKind(
stageDefinition.produces_kinds,
stageDefinition.produces ?? [],
dag.unitKinds?.get(unit) ?? null,
).length === 0
) {
fail(`prepare unit "${unit}" has no applicable required outputs for stage "${stage}"`);
}
}
assertUniqueSwarmBoltSlugs(dag.units);
// P7: the construction repo this batch targets. resolveConstructionRepo errors
// on a multi-repo intent with no --repo (forwarded as the batch failure), infers
// the lone repo for a single-repo intent, and yields cwd=projectDir for a legacy
// intent (today's behaviour). The repoCwd is where `--base` is derived from and
// is forwarded to every `aidlc-worktree create` so the worktree forks in-repo.
let repoCwd: string;
let repoName: string | null;
try {
const resolved = resolveConstructionRepo(projectDir, flags.repo, flags.intent, flags.space);
repoCwd = resolved.cwd;
repoName = resolved.repo;
} catch (e) {
fail(e instanceof Error ? e.message : String(e));
}
const base = flags.base ?? currentBranch(repoCwd);
const concurrency =
flags.concurrency && /^[1-9][0-9]*$/.test(flags.concurrency)
? flags.concurrency
: String(units.length);
const attempt = currentSwarmAttempt(projectDir);
if (!attempt) {
fail(
"prepare could not resolve the current stage attempt from state and audit",
);
}
// Record a loud downgrade BEFORE the batch-start row, if the conductor reports
// one. The driver-selection read (AIDLC_USE_SWARM) is conductor-side; the tool
// only learns a degrade happened via this flag.
if (flags["degraded-from"]) {
emitSwarmDegraded(
projectDir,
flags.batch,
flags["degraded-from"] as DriverName,
);
}
const prepared: {
unit: string;
ok: boolean;
worktree_path?: string;
error?: string;
}[] = [];
// Forward the RESOLVED repo name (not the raw flag) so every sibling primitive
// anchors to the same repo — an inferred lone repo is passed explicitly too, so
// create/merge/discard never re-resolve to a different repo than prepare chose.
const repoArgs = repoName ? ["--repo", repoName] : [];
for (const unit of units) {
const boltSlug = swarmBoltSlug(unit);
const created = runTool(
"aidlc-worktree.ts",
[
"create",
"--slug",
boltSlug,
"--base",
base,
"--swarm-unit",
unit,
"--swarm-batch",
flags.batch,
"--swarm-stage",
attempt.stage,
"--swarm-floor",
attempt.floor,
...repoArgs,
],
projectDir
);
if (!created.ok) {
prepared.push({
unit,
ok: false,
error: `worktree create failed: ${created.stderr.trim() || created.stdout.trim()}`,
});
continue;
}
let worktreeDir: string;
try {
worktreeDir = JSON.parse(created.stdout).worktree_path;
} catch {
prepared.push({
unit,
ok: false,
error: "could not parse worktree_path from aidlc-worktree create",
});
continue;
}
const started = runTool(
"aidlc-bolt.ts",
["start", "--worktree", "--slug", boltSlug, "--batch", flags.batch, "--name", unit, ...repoArgs],
projectDir
);
if (!started.ok) {
prepared.push({
unit,
ok: false,
worktree_path: worktreeDir,
error: `bolt start failed: ${started.stderr.trim() || started.stdout.trim()}`,
});
continue;
}
prepared.push({ unit, ok: true, worktree_path: worktreeDir });
}
// Stamp only worktrees this invocation actually created and started. Emitting
// before creation would let a failed re-prepare in a later stage attempt
// relabel an old preserved worktree with the current attempt, allowing stale
// data to pass finalize's exact-attempt check.
const readyUnits = prepared.filter((unit) => unit.ok).map((unit) => unit.unit);
if (readyUnits.length > 0) {
emitSwarmStarted(
projectDir,
flags.batch,
readyUnits,
dag.units,
concurrency,
attempt,
);
}
console.log(
JSON.stringify(
{
batch: flags.batch,
base,
concurrency: Number(concurrency),
units: prepared,
...(swarmChangeNotices.length > 0 ? { change_notices: swarmChangeNotices } : {}),
},
null,
2
)
);
// Exit 2 if any worktree failed to fork — the conductor must take the baton.
process.exit(prepared.some((p) => !p.ok) ? 2 : 0);
}
// --- check ------------------------------------------------------------------
function handleCheck(rest: string[]): void {
const { positional, flags } = parseArgs(rest);
const projectDir = resolveProjectDir(flags["project-dir"]);
const unit = positional[0] ?? flags.unit;
if (!unit) {
fail("check requires a unit name (positional `check <unit>` or --unit <unit>)");
}
swarmBoltSlug(unit);
if (!flags["check-cmd"]) {
fail("check requires --check-cmd <shell command; exit 0 = converged>");
}
const verdict = verdictFor(unit, projectDir, flags["check-cmd"], flags["test-file"]);
if (!verdict.exists) {
fail(`no worktree for unit "${unit}" — run \`prepare\` first`);
}
if (verdict.confineError) {
console.log(
JSON.stringify({
unit,
converged: false,
tampered: false,
reason: "error",
detail: verdict.confineError,
})
);
process.exit(1);
}
const genuine = verdict.converged && !verdict.tampered;
const out: Record<string, unknown> = {
unit,
converged: verdict.converged,
tampered: verdict.tampered,
reason: verdict.tampered ? "error" : null,
};
if (verdict.tampered) out.detail = "protected test file was modified";
console.log(JSON.stringify(out));
// Exit 0 ONLY for a genuine convergence — the seam the ultracode script and
// the conductor gate on (a worker's self-claim is never read).
process.exit(genuine ? 0 : 1);
}
// --- finalize ---------------------------------------------------------------
function handleFinalize(rest: string[]): void {
const { positional, flags } = parseArgs(rest);
const projectDir = resolveProjectDir(flags["project-dir"]);
const batch = flags.batch ?? positional[0];
if (!batch || !/^[1-9][0-9]*$/.test(batch)) {
fail("finalize requires --batch <positive integer>");
}
if (!flags["check-cmd"]) {
fail("finalize requires --check-cmd <shell command; exit 0 = converged>");
}
const claimed = flags.claimed ? splitCsv(flags.claimed) : [];
// The universe of units in the batch; defaults to the claimed set when the
// conductor passes only --claimed (then declined-unit accounting is a no-op).
const allUnits = flags.units ? splitCsv(flags.units) : claimed.slice();
const dag = resolveBoltDag(projectDir, flags.intent, flags.space);
if (dag.state !== "ok") fail("finalize requires a current resolved Unit DAG");
const currentStage = (getField(readStateFile(projectDir), "Current Stage") ?? "")
.trim()
.toLowerCase()
.replace(/\s+/g, "-");
const stageDefinition = resolveStage(currentStage);
for (const unit of new Set([...allUnits, ...claimed])) {
swarmBoltSlug(unit);
if (!dag.units.includes(unit)) {
fail(`finalize unit "${unit}" is not in the current resolved Unit DAG`);
}
if (
stageDefinition &&
filterProducesByKind(
stageDefinition.produces_kinds,
stageDefinition.produces ?? [],
dag.unitKinds?.get(unit) ?? null,
).length === 0
) {
fail(`finalize unit "${unit}" has no applicable required outputs for stage "${currentStage}"`);
}
}
const claimedSet = new Set(claimed);
const testFile = flags["test-file"];
const checkCmd = flags["check-cmd"];
const review = reviewerRequirement(projectDir);
const currentAttempt = currentSwarmAttempt(projectDir);
// Optional per-declined-unit typed reasons: `--reasons a=unsatisfiable,b=budget-exhausted`.
// The conductor judged WHY each unclaimed unit gave up (knowledge → conductor,
// D-I); the tool records that attribution faithfully (determinism → tool),
// mirroring how --claimed / --degraded-from carry conductor decisions. Applies
// ONLY to declined units — a claimed unit's reason is always the tool's own
// re-verify verdict, so the lying-conductor guard cannot be talked out of an
// `error`. Unparseable / out-of-enum entries are rejected loudly rather than
// silently downgraded; an unlisted declined unit defaults to `cap-exhausted`.
const declinedReasons: Record<string, FailureReason> = {};
if (flags.reasons) {
for (const pair of splitCsv(flags.reasons)) {
const eq = pair.indexOf("=");
if (eq <= 0) {
fail(`--reasons entry must be <unit>=<reason>: "${pair}"`);
}
const unit = pair.slice(0, eq).trim();
swarmBoltSlug(unit);
const reason = pair.slice(eq + 1).trim() as FailureReason;
if (!DECLINED_REASONS.includes(reason)) {
fail(`--reasons reason for "${unit}" must be one of: ${DECLINED_REASONS.join(", ")}`);
}
declinedReasons[unit] = reason;
}
}
// Re-verify every claimed unit (the lying-conductor guard) and account for any
// declined unit the conductor did not claim.
const results: UnitResult[] = [];
const genuine: string[] = [];
const preparedAttempts = new Map<string, SwarmAttemptStamp>();
const sourceBindings = new Map<string, SourceBinding>();
const recordSnapshots = new Map<string, ReviewedRecordSnapshot>();
const sourceFreshnessBypassed =
process.env.AIDLC_SKIP_SOURCE_FRESHNESS === "1";
const recoveryBudget = newGitlinkRecoveryBudget();
for (const unit of allUnits) {
if (claimedSet.has(unit)) {
const verdict = verdictFor(unit, projectDir, checkCmd, testFile);
const preparedAttempt = preparedSwarmAttempt(
projectDir,
batch,
unit,
);
if (!preparedAttempt) {
results.push({
unit,
status: "failed",
reason: "error",
detail:
"no stamped SWARM_STARTED boundary for this unit and batch; run prepare in the current attempt",
});
} else if (
!currentAttempt ||
preparedAttempt.stage !== currentAttempt.stage ||
preparedAttempt.floor !== currentAttempt.floor
) {
results.push({
unit,
status: "failed",
reason: "error",
detail:
`prepared swarm attempt ${preparedAttempt.stage}/${preparedAttempt.floor} ` +
`does not match the current attempt ` +
`${currentAttempt ? `${currentAttempt.stage}/${currentAttempt.floor}` : "(unresolved)"}`,
});
} else if (!verdict.exists) {
results.push({
unit,
status: "failed",
reason: "error",
detail: "no worktree on re-verify (prepare not run?)",
});
} else if (verdict.confineError) {
results.push({ unit, status: "failed", reason: "error", detail: verdict.confineError });
} else if (verdict.tampered) {
results.push({
unit,
status: "failed",
reason: "error",
detail: "convergence rejected: protected test file was modified",
tampered: true,
});
} else if (verdict.converged) {
const receipt: ReceiptCheck = review.error
? { error: review.error }
: review.reviewer
? reviewerReceiptError(
projectDir,
unit,
review.stage,
review.reviewer,
review.reviewClass,
review.maxIterations,
)
: { error: null };
if (receipt.error) {
results.push({
unit,
status: "failed",
reason: "error",
detail: receipt.error,
});
} else {
const captured = stageDefinition
? captureReviewedRecordSnapshot(
projectDir,
unit,
stageDefinition,
receipt,
)
: { error: `cannot resolve stage "${currentStage}"` };
const bound = receipt.sourceFingerprint
? bindReviewedSource(
projectDir,
swarmBoltSlug(unit),
receipt.sourceFingerprint,
recoveryBudget,
)
: {};
if (captured.error || !captured.snapshot) {
results.push({
unit,
status: "failed",
reason: "error",
detail: captured.error ?? `cannot snapshot record artifacts for unit "${unit}"`,
});
} else if (bound.error) {
results.push({
unit,
status: "failed",
reason: "error",
detail: bound.error,
});
} else {
if (bound.binding) sourceBindings.set(unit, bound.binding);
recordSnapshots.set(unit, captured.snapshot);
genuine.push(unit);
preparedAttempts.set(unit, preparedAttempt);
results.push({ unit, status: "converged" });
}
}
} else {
// Claimed converged, but the check command does not pass on re-verify —
// the lying / misremembering conductor. Refuse the merge.
results.push({
unit,
status: "failed",
reason: "error",
detail: "claimed converged but the check command did not pass on re-verify",
});
}
} else {
// The conductor did not claim this unit: its driver loop ended without
// convergence. The conductor may attribute a typed reason via --reasons
// (e.g. `unsatisfiable` when it judged the unit fundamentally unbuildable,
// `budget-exhausted` when the ultracode token ceiling stopped it); absent
// an attribution, `cap-exhausted` is the catch-all (the loop ended without
// convergence and the conductor offered no finer classification).
const reason = declinedReasons[unit] ?? "cap-exhausted";
results.push({
unit,
status: "failed",
reason,
detail:
reason === "cap-exhausted"
? "unit not claimed converged by the conductor"
: `unit not claimed converged; conductor attributed: ${reason}`,
});
}
}
// Serialised HOLD-MERGE merge-back of the genuine passes only (sorted for a
// deterministic merge order). release-merge is idempotent — safe whether or not
// the lock was ever held; complete --merge reaches the add/add-conflict abort
// pinned at the composed surface by the worktree-merge tests.
const mergeFailures: { unit: string; detail: string }[] = [];
for (const unit of [...genuine].sort()) {
const boltSlug = swarmBoltSlug(unit);
const recordSnapshot = recordSnapshots.get(unit);
const recordMergeError = recordSnapshot
? mergeReviewedRecordSnapshot(projectDir, unit, recordSnapshot)
: `reviewed record snapshot is missing for unit "${unit}"`;
if (recordMergeError !== null) {
mergeFailures.push({ unit, detail: recordMergeError });
continue;
}
runTool("aidlc-bolt.ts", ["release-merge", "--slug", boltSlug], projectDir);
const merged = runTool(
"aidlc-bolt.ts",
["complete", "--merge", "--slug", boltSlug, "--batch", batch, "--name", unit],
projectDir
);
if (!merged.ok) {
mergeFailures.push({ unit, detail: merged.stderr.trim() || merged.stdout.trim() });
}
}
// Authoritative audit trail: one row per unit, the baton per failed unit, the
// batch tally to close. A converged unit whose merge-back FAILED gets no
// SWARM_UNIT_CONVERGED row: that row is the engine's batch-advance signal, and
// emitting it for a unit whose metadata never landed on main would advance the
// run past an unmerged unit. It gets no SWARM_UNIT_FAILED row either - the
// unit did converge; the failure envelope + exit 2 carry the merge outcome.
// The row lands when a finalize retry scoped to that unit merges cleanly (the
// worktree is preserved and release-merge is idempotent, so the retry is a
// pure re-invocation - no prepare).
const mergeFailed = new Set(mergeFailures.map((f) => f.unit));
for (const r of results) {
if (r.status === "converged") {
if (!mergeFailed.has(r.unit)) {
const attempt = preparedAttempts.get(r.unit);
if (attempt) {
emitUnitConverged(
projectDir,
batch,
r.unit,
attempt,
sourceBindings.get(r.unit),
sourceFreshnessBypassed,
);
}
}
} else {
emitUnitFailed(projectDir, batch, r.unit, r.reason ?? "error");
emitBoltFailed(projectDir, r.unit, r.detail ?? `unit "${r.unit}" failed: ${r.reason}`);
}
}
const failedResults = results.filter((r) => r.status === "failed");
for (const r of failedResults) {
emitBatonReturned(projectDir, batch, r.unit, r.reason ?? "error");
}
const convergedCount = genuine.length;
const failedCount = failedResults.length;
emitSwarmCompleted(projectDir, batch, convergedCount, failedCount);
const envelope = {
batch,
units: results.map((result) => ({
...result,
bolt_slug: swarmBoltSlug(result.unit),
})),
converged: convergedCount,
failed: failedCount,
merge_failures: mergeFailures,
};
console.log(JSON.stringify(envelope, null, 2));
// Exit 2 signals "the conductor must take the baton" (a unit failed or a merge
// failed); exit 0 means every claimed unit was genuinely converged and merged.
process.exit(failedCount > 0 || mergeFailures.length > 0 ? 2 : 0);
}
// --- shared helpers ---------------------------------------------------------
function splitCsv(value: string): string[] {
return value
.split(",")
.map((u) => u.trim())
.filter((u) => u !== "");
}
function swarmBoltSlug(unit: string): string {
const unitNameError = validateUnitName(unit);
if (unitNameError) fail(unitNameError);
return boltSlugForUnit(unit);
}
function assertUniqueSwarmBoltSlugs(units: string[]): void {
const owners = new Map<string, string>();
for (const unit of units) {
const boltSlug = swarmBoltSlug(unit);
const existing = owners.get(boltSlug);
if (existing && existing !== unit) {
fail(
`Units "${existing}" and "${unit}" resolve to the same internal Bolt slug ` +
`"${boltSlug}". Rename one Unit before starting the autonomous swarm.`,
);
}
owners.set(boltSlug, unit);
}
}
function currentSwarmAttempt(projectDir: string): SwarmAttemptStamp | null {
try {
const stage =
getField(readStateFile(projectDir), "Current Stage")?.trim() ?? "";
if (!stage) return null;
return {
stage,
floor: latestMainWorkflowStageRunFloorForProject(projectDir, stage),
};
} catch {
return null;
}
}
function preparedSwarmAttempt(
projectDir: string,
batch: string,
unit: string,
): SwarmAttemptStamp | null {
const matching = readAuditShardEvents(projectDir).filter((event) => {
if (event.event !== "SWARM_STARTED") return false;
if (auditBlockField(event.block, "Batch number") !== batch) return false;
const units = splitCsv(auditBlockField(event.block, "Unit names") ?? "");
return units.includes(unit);
});
const stamped = matching.filter(
(event) =>
auditBlockField(event.block, "Stage") !== null &&
auditBlockField(event.block, "Run floor") !== null,
);
if (stamped.length > 0) {
stamped.sort((a, b) => {
if (a.timestamp !== b.timestamp) {
return a.timestamp < b.timestamp ? -1 : 1;
}
if (a.shardIndex !== b.shardIndex) return a.shardIndex - b.shardIndex;
return a.pos - b.pos;
});
const timestamp = stamped[stamped.length - 1].timestamp;
const latest = stamped.filter((event) => event.timestamp === timestamp);
const stamps = new Map<string, SwarmAttemptStamp>();
for (const event of latest) {
const stage = auditBlockField(event.block, "Stage");
const floor = auditBlockField(event.block, "Run floor");
if (!stage || !floor) continue;
stamps.set(`${stage}\0${floor}`, { stage, floor });
}
// Same-second starts in different shards are unordered. A shared stamp is
// harmless; differing stamps fail closed instead of picking by filename.
if (
new Set(latest.map((event) => event.shard)).size > 1 &&
stamps.size !== 1
) {
return null;
}
return stamps.values().next().value ?? null;
}
return legacyPreparedSwarmAttempt(projectDir, batch, unit);
}
function legacyPreparedSwarmAttempt(
projectDir: string,
batch: string,
unit: string,
): SwarmAttemptStamp | null {
const boltSlug = swarmBoltSlug(unit);
const wt = worktreePath(projectDir, boltSlug);
const recordPrefix = relativeRecordDir(projectDir);
const wtState = worktreeStateFilePath(wt, recordPrefix);
const wtAudit = worktreeAuditFilePath(wt, recordPrefix, projectDir);
const wtRuntime = worktreeRuntimeGraphPath(wt, recordPrefix);
if (
!existsSync(wt) ||
!isRegularFile(wtState) ||
!isRegularFile(wtAudit) ||
!isRegularFile(wtRuntime)
) {
return null;
}
let worktreeAudit: string;
let state: string;
try {
worktreeAudit = readFileSync(wtAudit, "utf-8");
state = readFileSync(wtState, "utf-8");
} catch {
return null;
}
const fork = findAllEvents(worktreeAudit, "AUDIT_FORKED")
.filter((event) => auditBlockField(event.block, "Bolt slug") === boltSlug)
.at(-1);
const boundaryRaw = fork ? auditBlockField(fork.block, "Fork Boundary") : null;
const sourceHash = fork ? auditBlockField(fork.block, "Source Audit Hash") : null;
if (!boundaryRaw || !sourceHash || !/^[0-9]+$/.test(boundaryRaw)) return null;
const mainDir = auditShardDir(projectDir);
if (!mainDir) return null;
const mainShard = join(mainDir, basename(wtAudit));
let mainBytes: Buffer;
try {
mainBytes = readFileSync(mainShard);
} catch {
return null;
}
const boundary = Number(boundaryRaw);
if (!Number.isSafeInteger(boundary) || boundary < 0 || mainBytes.length < boundary) {
return null;
}
const frozenBytes = mainBytes.subarray(0, boundary);
if (createHash("sha256").update(frozenBytes).digest("hex") !== sourceHash) {
return null;
}
const frozenAudit = frozenBytes.toString("utf-8");
const frozenBlocks = frozenAudit.replace(/\r\n/g, "\n").split(/\n---\n/);
const legacyStarts: number[] = [];
const boltStarts: number[] = [];
const stateForks: number[] = [];
for (let index = 0; index < frozenBlocks.length; index++) {
const block = frozenBlocks[index];
const event = auditBlockField(block, "Event");
if (
event === "SWARM_STARTED" &&
auditBlockField(block, "Batch number") === batch &&
!auditBlockField(block, "Stage") &&
!auditBlockField(block, "Run floor") &&
splitCsv(auditBlockField(block, "Unit names") ?? "").includes(unit)
) {
legacyStarts.push(index);
}
if (
event === "BOLT_STARTED" &&
auditBlockField(block, "Batch number") === batch &&
auditBlockField(block, "Bolt slug") === boltSlug
) {
boltStarts.push(index);
}
if (
event === "STATE_FORKED" &&
auditBlockField(block, "Bolt slug") === boltSlug
) {
stateForks.push(index);
}
}
const hasPreparationSequence = legacyStarts.some((started) =>
boltStarts.some((bolt) =>
bolt > started && stateForks.some((forked) => forked > bolt),
),
);
if (!hasPreparationSequence) return null;
const stage = getField(state, "Current Stage")?.trim() ?? "";
if (!stage) return null;
return {
stage,
floor: latestMainWorkflowStageRunFloor(frozenAudit, stage),
};
}
function currentBranch(projectDir: string): string {
const r = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
cwd: projectDir,
encoding: "utf-8",
});
return (r.stdout ?? "main").trim() || "main";
}
function fail(msg: string): never {
console.error(JSON.stringify({ error: msg }));
process.exit(1);
}
export function main(argv: string[]): void {
// The subcommand is the first bare token that is NOT a flag NOR a flag's value.
// Walk argv skipping `--flag value` / `--flag=value` pairs so
// `--project-dir <path> check ...` and `check --project-dir <path> ...` both
// resolve to `check`. The handlers re-read every flag from `rest`, and a
// positional unit (e.g. `check <unit>`) survives in rest.
let subcommand: string | undefined;
let subIndex = -1;
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a.startsWith("--")) {
if (!a.includes("=") && i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
i++;
}
continue;
}
subcommand = a;
subIndex = i;
break;
}
const rest = subIndex >= 0 ? [...argv.slice(0, subIndex), ...argv.slice(subIndex + 1)] : argv;
switch (subcommand) {
case "prepare":
handlePrepare(rest);
break;
case "check":
handleCheck(rest);
break;
case "finalize":
handleFinalize(rest);
break;
default:
console.error(
JSON.stringify({
error: `Unknown subcommand: ${subcommand ?? "(none)"}. Valid: prepare, check, finalize`,
})
);
process.exit(1);
}
}
if (import.meta.main) main(process.argv.slice(2));