/** * `release-composition` — the operator's explicit lifecycle for a project's * active release (#850). * * A reliability patch is only trustworthy while nothing unrelated slips into * it, so a project can declare one active release composition: a campaign * identifier plus the exact PRs allowed into it. While one is active, * `conductor_pr_merge` refuses every other PR with the * `outside-active-release` refusal — this command family is the only way to * move that guard: `declare` it, admit exactly one extra PR with `override`, * or retire it with `complete` / `cancel`. Every transition is written to the * store as durable state that survives daemon restarts and recorded as an * immutable material event as its audit trail; a merge call's free-form * reason is never an override. */ import type { CommandContext } from "./context.ts"; import { findProject, loadConfig } from "../config.ts"; import { dbPath, openStore } from "../store.ts"; /** Every `--pr URL` / `--pr=URL` value in the invocation tail, in order. */ function prUrls(argv: readonly string[]): string[] { const urls: string[] = []; for (let i = 0; i < argv.length; i++) { const token = argv[i]!; if (token === "--pr") { const value = argv[i + 1]; if (value !== undefined && !value.startsWith("-")) { urls.push(value); i++; } } else if (token.startsWith("--pr=")) { urls.push(token.slice("--pr=".length)); } } return urls; } /** A full pull-request URL — the exact shape the merge gate compares against. */ function isFullPrUrl(url: string): boolean { return /^https:\/\/[^/]+\/.+\/pull\/\d+$/.test(url); } const USAGE_LINE = "omp-conductor: release-composition needs a subcommand: " + "declare | override | complete | cancel | status\n"; export async function releaseCompositionCommand(ctx: CommandContext): Promise { const sub = ctx.argv[1]; if (sub === undefined || sub.length === 0 || sub.startsWith("-")) { process.stderr.write(USAGE_LINE); process.exit(2); } // Argument shape is validated before anything opens: a malformed invocation // never touches the store, and every store-backed refusal below still runs // the `close()` in the finally before the process exits. let campaign: string | undefined; let urls: string[] = []; if (sub === "declare") { campaign = ctx.flag("campaign"); if (campaign === undefined || campaign.length === 0) { process.stderr.write( "omp-conductor: release-composition declare needs --campaign ID, e.g. v0.18.1-reliability\n", ); process.exit(2); } urls = prUrls(ctx.argv.slice(2)); const malformed = urls.filter((url) => !isFullPrUrl(url)); if (malformed.length > 0) { process.stderr.write( "omp-conductor: --pr takes full pull-request URLs like https://github.com/owner/repo/pull/7; " + `refusing: ${malformed.join(", ")}\n`, ); process.exit(2); } } if (sub === "override") { urls = prUrls(ctx.argv.slice(2)); if (urls.length !== 1 || !isFullPrUrl(urls[0]!)) { process.stderr.write( "omp-conductor: release-composition override needs exactly one full --pr URL " + "(https://github.com/owner/repo/pull/N)\n", ); process.exit(2); } } const cfg = loadConfig(); const project = findProject(cfg, ctx.projectFlag); const store = openStore(dbPath()); let failed = false; try { const now = Date.now(); switch (sub) { case "status": { const active = store.activeReleaseComposition(project.name); if (active === undefined) { process.stdout.write( `${project.name}: no active release composition — merges are ungated by release scope.\n`, ); break; } const overrides = store.releaseCompositionOverrides(project.name, active.campaign); process.stdout.write( `${project.name}: active release "${active.campaign}", declared ` + `${new Date(active.declaredAt).toISOString()}${active.declaredBy ? ` by ${active.declaredBy}` : ""}. ` + `Allowed PRs (${active.allowedPrUrls.length}): ` + `${active.allowedPrUrls.length === 0 ? "none yet" : active.allowedPrUrls.join(", ")}. ` + `Operator overrides: ${overrides.length === 0 ? "none" : overrides.map((o) => o.prUrl).join(", ")}. ` + "conductor_pr_merge refuses every other PR until complete|cancel.\n", ); break; } case "declare": { const conflict = store.declareReleaseComposition(project.name, { campaign: campaign!, allowedPrUrls: urls, declaredBy: "operator", declaredAt: now, }); if (conflict !== undefined) { process.stderr.write( `release-composition: ${project.name} already has an active release "${conflict.campaign}" ` + `(declared ${new Date(conflict.declaredAt).toISOString()}, ${conflict.allowedPrUrls.length} PR(s) allowed). ` + "One release at a time: complete or cancel it first with " + "`omp-conductor release-composition complete|cancel`.\n", ); failed = true; break; } store.recordMaterialEvent({ project: project.name, category: "release-composition", summary: `operator declared release ${campaign} (${urls.length} PR(s) allowed)`, evidence: `An operator declared the active release composition "${campaign}" for ${project.name} with ` + `${urls.length} allowed PR(s): ${urls.length === 0 ? "none yet" : urls.join(", ")}. While it is ` + "active, conductor_pr_merge refuses every other PR with outside-active-release until the " + "release completes, is cancelled, or a PR is explicitly overridden.", occurredAt: now, recordedAt: now, }); process.stdout.write( `release-composition: ${project.name} now assembling release "${campaign}" with ` + `${urls.length} allowed PR(s). Unrelated merges are refused until complete|cancel.\n`, ); break; } case "override": { const active = store.activeReleaseComposition(project.name); if (active === undefined) { process.stderr.write( `release-composition: ${project.name} has no active release to override — ` + "merges are already ungated by release scope.\n", ); failed = true; break; } const reason = ctx.flag("reason") ?? "operator-override"; const granted = store.grantReleaseCompositionOverride(project.name, { campaign: active.campaign, prUrl: urls[0]!, by: "operator", reason, at: now, }); store.recordMaterialEvent({ project: project.name, category: "release-composition", summary: `operator overrode release ${active.campaign} for ${granted.prUrl}`, evidence: `An operator admitted ${granted.prUrl} into the active release "${active.campaign}" of ` + `${project.name} with reason "${reason}". The override bypasses only the composition check — ` + "authority, pause, base-red-freeze, exact-head, green-checks and single-flight still apply.", occurredAt: now, recordedAt: now, }); process.stdout.write( `release-composition: ${granted.prUrl} is now allowed into release "${active.campaign}" ` + `in ${project.name} (reason: ${reason}). Every other merge gate still applies.\n`, ); break; } case "complete": case "cancel": { const retired = sub === "complete" ? store.completeReleaseComposition( project.name, "operator", ctx.flag("reason") ?? "operator-completed", now, ) : store.cancelReleaseComposition( project.name, "operator", ctx.flag("reason") ?? "operator-cancelled", now, ); if (!retired) { process.stdout.write( `${project.name}: no active release composition — nothing to ${sub}. Merges are ungated by release scope.\n`, ); break; } store.recordMaterialEvent({ project: project.name, category: "release-composition", summary: `operator ${sub === "complete" ? "completed" : "cancelled"} the active release`, evidence: `An operator ${sub}d the active release composition of ${project.name} with reason ` + `"${ctx.flag("reason") ?? `operator-${sub}d`}". conductor_pr_merge merges are no longer ` + "restricted by that release's scope; the audit trail keeps the closed row and its overrides.", occurredAt: now, recordedAt: now, }); process.stdout.write( `release-composition: ${project.name}'s active release ${sub}d. Merges are no longer gated ` + "by release scope; overrides stay in the audit trail scoped to their campaign.\n", ); break; } default: process.stderr.write(USAGE_LINE); process.exit(2); } } finally { store.close(); } if (failed) process.exit(2); }