/*
RFC-0866: leitstand.certify command handler — produces GateDecisionV1 JSON via certification orchestration primitives.
- Does not perform deployment — that is the responsibility of deploy-execution.ts.
- Does not define the GateDecisionV1 schema — that lives in certification/contracts/decisions.ts.
- Does not register the command — that lives in leitstand.module.ts.
- RFC-1249: wire werkstatt.commands.validate into packages.check + retire CMD-OUTPUT debt
- RFC-1284: crash-safe access protection — suspend-before-measure via durable accessPinSuspended marker, stale-marker entry resume (flag=true), finally-restore, pinManagement {suspended,resumed,staleResumed,restoreError}, isProtectedLiveTarget gating, cache-clone marker store (Q-6), flag=false+suspended proceeds without resume (Q-7)
- RFC-1295
- RFC-1325: provisional workers.dev as first-class site host
Single host predicate module in werkstatt-shared; provisional registration derives dev/alt/main workers.dev channels from the account subdomain; first dev deploy auto-protects provisional hosts; provisional main responses carry noindex; dns-aid skips provisional sites; site.domain.adopt cuts over to a custom domain.
- RFC-1324: certify diagnostics surface the inner command summary — mission.check / editorial.review.verify failures name the real reason instead of a bare exit code
RECOVERY-2026, REVIEW-CODE-2026, RFC-0866, RFC-0867, RFC-0929, RFC-0938, RFC-1097, RFC-1126, RFC-1149, RFC-1190, RFC-1207
*/
import path from "node:path";
import type {
KernelCommandInput,
KernelRuntimeContext,
KernelCommandResult,
} from "@warpgogol/werkstatt-engine/kernel";
import { writeFileIfChanged } from "@warpgogol/werkstatt-engine/kernel";
import type { Sha256Digest } from "@warpgogol/werkstatt-shared/fingerprint";
import type {
GateDecisionV1,
MainVerificationDecisionV1,
} from "../certification/contracts/decisions.ts";
import type { GateChannel } from "../certification/contracts/identifiers.ts";
import type { DeploymentOperationState } from "../certification/state-machine.ts";
import { astroCertificationProfile } from "../certification/profile/astro-profile.ts";
import { makeR2ConfigFromEnv, resolveArtifactHash, flagSite } from "./deploy-helpers.ts";
import {
provisionCandidatePreview,
type CandidatePreview,
type CertifyTarget,
} from "./candidate-preview.ts";
import {
resolveCacheClonePath,
readSystemState,
readSystemConfigSmart,
} from "../sternsystem/registry-io.ts";
import { cacheCloneCommit } from "../mission/mission-git-commit.ts";
import { gitExec } from "../werkstatt/git-exec.ts";
import { createR2StorageAdapter } from "../certification/storage/r2-adapter.ts";
import {
planProducers,
executeProducers,
DEFAULT_PRODUCER_CONFIG,
type ProducerDependencyNodeV1,
type ProducerExecutionHandlerV1,
type ProducerExecutionInputV1,
} from "../certification/orchestration/orchestrator.ts";
import { evaluateCertificationDecision } from "../certification/aggregation.ts";
import type { EvidenceEnvelopeV1 } from "../certification/contracts/evidence.ts";
import { evidenceEnvelopeV1Schema } from "../certification/contracts/evidence.ts";
import type { ReleaseCandidateV1 } from "../certification/contracts/candidate.ts";
import type { CertificationPolicyBundleV1 } from "../certification/contracts/policy-bundle.ts";
import { gateDecisionV1Schema } from "../certification/contracts/decisions.ts";
import { createDefaultIO, type WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
import { isDevOrAltHost } from "@warpgogol/werkstatt-shared/middleware/site-host";
const PIN_DELETION_PROPAGATION_DELAY_MS = 5000;
// 2026-10-04 recovery fix: a cold mission.check (110+ captures, fresh evidence
// dir) takes ~7-11 minutes — the previous inner 5min race / outer 300s
// orchestrator timeout always expired mid-capture, racing a "timed out" fail
// decision against an "incomplete" no-decision return while the abandoned
// in-process mission.check kept running. 25min covers worst-case capture; the
// orchestrator timeout derives from it (+60s) so the inner race resolves first
// and still writes a diagnostic-bearing fail decision rather than no decision.
const MISSION_CHECK_TIMEOUT_MS = 25 * 60 * 1000;
function flagString(input: KernelCommandInput, key: string): string | undefined {
const v = input.flags[key];
return typeof v === "string" ? v : undefined;
}
function flagBoolean(input: KernelCommandInput, key: string): boolean {
const v = input.flags[key];
return v === true || v === "true";
}
interface EvidenceCacheEntry {
artifactHash: Sha256Digest;
evidence: EvidenceEnvelopeV1[];
producedAt: string;
freshnessExpiresAt: string;
sourceGate: GateChannel;
}
interface EvidenceCacheSidecar {
schema: "werkstatt/evidence-cache@1";
releaseId: string;
artifactHash: Sha256Digest;
evidence: EvidenceEnvelopeV1[];
producedAt: string;
producedByGate: GateChannel;
}
async function tryReuseEvidence(
cacheCloneDir: string,
releaseId: string,
artifactHash: Sha256Digest,
forceRequested: boolean,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
// RFC-0867: The sequential deployment pipeline (dev → alt → main) uses the same
// certification profile and policy bundle for all gates of a given release.
// The artifact hash (policyBundleRoot) match is therefore sufficient to guarantee
// that reused evidence satisfies the current gate's requirements. If the profile
// or policy bundle changes between gates, a superseding RFC must add a profile-hash
// check here.
if (forceRequested) return null;
const gateDecisionsDir = path.join(cacheCloneDir, "gate-decisions");
let entries: string[];
try {
entries = (await io.readdir(gateDecisionsDir)).map((e) => e.name);
} catch {
return null;
}
const now = new Date();
const gateSuffixes = ["dev", "alt", "main"];
for (const gate of gateSuffixes) {
const decisionFile = entries.find((e) => e === `${releaseId}-${gate}.json`);
if (!decisionFile) continue;
let decision: GateDecisionV1;
try {
const content = await io.readFile(path.join(gateDecisionsDir, decisionFile));
const parsed = JSON.parse(content);
const result = gateDecisionV1Schema.safeParse(parsed);
if (!result.success) continue;
decision = result.data;
} catch {
continue;
}
if (decision.status !== "pass") continue;
if (decision.policyBundleRoot !== artifactHash) continue;
const sidecarFile = entries.find((e) => e === `${releaseId}-evidence.json`);
if (!sidecarFile) continue;
let sidecar: EvidenceCacheSidecar;
try {
const sidecarContent = await io.readFile(path.join(gateDecisionsDir, sidecarFile));
const sidecarParsed = JSON.parse(sidecarContent);
if (
sidecarParsed.schema !== "werkstatt/evidence-cache@1" ||
sidecarParsed.artifactHash !== artifactHash ||
!Array.isArray(sidecarParsed.evidence)
) {
continue;
}
const validatedEvidence: EvidenceEnvelopeV1[] = [];
for (const env of sidecarParsed.evidence) {
const envResult = evidenceEnvelopeV1Schema.safeParse(env);
if (!envResult.success) continue;
validatedEvidence.push(envResult.data);
}
if (validatedEvidence.length === 0) continue;
sidecar = {
schema: sidecarParsed.schema,
releaseId: sidecarParsed.releaseId,
artifactHash: sidecarParsed.artifactHash,
evidence: validatedEvidence,
producedAt: sidecarParsed.producedAt,
producedByGate: sidecarParsed.producedByGate,
};
} catch {
continue;
}
const freshEvidence = sidecar.evidence.filter((env) => {
if (!env.freshness?.expiresAt) return false;
return new Date(env.freshness.expiresAt) > now;
});
if (freshEvidence.length === 0) continue;
const earliestExpiry = freshEvidence.map((env) => env.freshness.expiresAt).sort()[0];
return {
artifactHash,
evidence: freshEvidence,
producedAt: sidecar.producedAt,
freshnessExpiresAt: earliestExpiry,
sourceGate: gate as GateChannel,
};
}
return null;
}
async function writeEvidenceSidecar(
cacheCloneDir: string,
releaseId: string,
artifactHash: Sha256Digest,
evidence: EvidenceEnvelopeV1[],
producedAt: string,
producedByGate: GateChannel,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
const sidecar: EvidenceCacheSidecar = {
schema: "werkstatt/evidence-cache@1",
releaseId,
artifactHash,
evidence,
producedAt,
producedByGate,
};
const outputDir = path.join(cacheCloneDir, "gate-decisions");
await io.mkdir(outputDir);
const sidecarPath = path.join(outputDir, `${releaseId}-evidence.json`);
await writeFileIfChanged(sidecarPath, JSON.stringify(sidecar, null, 2) + "\n");
return sidecarPath;
}
export interface CertifyInput {
systemId: string;
gate: "dev" | "alt" | "main";
candidateId: string;
artifactHash: Sha256Digest;
releaseId: string;
}
export interface PinManagement {
autoManaged: boolean;
suspended: boolean;
resumed: boolean;
staleResumed: boolean;
restoreError: string | null;
}
export interface CertifyResult {
command: "leitstand.certify";
systemId: string;
gate: string;
decisionId: string;
status: "pass" | "fail" | "stale" | "incomplete";
outputPath: string;
mainVerificationPath?: string;
producerCount: number;
evidenceCount: number;
pinManagement?: PinManagement;
// RFC-1207: measurement-target provenance — which artifact the gate evaluated.
measurementTarget?: "candidate-preview" | "channel" | "explicit";
measuredReleaseId?: string;
/** Candidate-preview identity. Present on every candidate-mode run; baseUrl/port only when a preview was actually provisioned (evidence-reuse runs skip the boot). */
preview?: {
artifactHash: string;
baseUrl?: string;
port?: number;
};
}
export async function runLeitstandCertify(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise> {
const systemId = flagSite(input);
if (!systemId) throw new Error("[leitstand.certify] --site is required");
const gate = flagString(input, "gate");
if (!gate) throw new Error("[leitstand.certify] --gate is required (dev | alt | main)");
if (!["dev", "alt", "main"].includes(gate)) {
throw new Error(`[leitstand.certify] --gate must be dev, alt, or main (got: ${gate})`);
}
const candidateId = flagString(input, "candidate-id") ?? systemId;
const artifactHashFlag = flagString(input, "artifact-hash");
const releaseId = flagString(input, "release");
if (!releaseId) throw new Error("[leitstand.certify] --release is required");
const releaseDir = path.join(context.workspaceRoot, "releases", releaseId);
const {
hash: artifactHash,
source: artifactHashSource,
path: artifactHashPath,
} = await resolveArtifactHash(artifactHashFlag, releaseDir);
context.logger.info(
`[certify] artifact hash resolved from ${artifactHashSource}${artifactHashPath ? ` (${artifactHashPath})` : ""}: ${artifactHash}`,
);
const baseUrlFlag = flagString(input, "base-url");
const gateChannel = gate as GateChannel;
// RFC-1207: measurement target resolution — `--base-url` is the explicit
// override (verbatim, unchanged); `--target channel` keeps the legacy
// channel-measuring audit path; the default `candidate` mode provisions a
// local `wrangler dev` preview of the candidate artifact inside certifyCore.
const targetFlag = flagString(input, "target");
if (targetFlag !== undefined && targetFlag !== "candidate" && targetFlag !== "channel") {
throw new Error(
`[leitstand.certify] --target must be "candidate" or "channel" (got: ${targetFlag})`,
);
}
const certifyTarget: CertifyTarget = targetFlag === "channel" ? "channel" : "candidate";
// RFC-0938: Auto-manage access PIN. Default: true. Post-RFC-1284 the flag
// controls suspend-before-measure / resume-after and crash recovery — under
// flag=false an entry suspension marker is left in place and the run
// proceeds on the already-open channel (REVIEW-CODE-2026-10-15-02 F2,
// decisions Q-7).
const autoManagePinRaw = input.flags["auto-manage-pin"];
const autoManagePin = autoManagePinRaw === false || autoManagePinRaw === "false" ? false : true;
// RFC-1284: crash recovery on entry. A stale accessPinSuspended marker means
// a previous certify died between secret deletion and restore — the channel
// is currently open and desired protection is unmet. Resume unconditionally
// when --auto-manage-pin is in effect so the next certify always re-asserts
// the projection. The cache clone owns the marker — non-smart state reads
// keep every access-touching command on one store (decisions Q-6).
const systemState = await readSystemState(context.workspaceRoot, systemId);
const recordedPin = systemState.accessPin;
// `?? null` — loose-schema extension keys may be undefined in un-normalized reads.
let suspensionActive = (systemState.accessPinSuspended ?? null) !== null;
let pinSuspended = false;
let pinResumed = false;
let staleResumed = false;
let pinRestoreError: string | null = null;
const { executeKernelCommand } = await import("@warpgogol/werkstatt-engine/kernel");
if (suspensionActive && autoManagePin) {
context.logger.info(
`[leitstand.certify] stale access-PIN suspension detected (by ${systemState.accessPinSuspended?.by} at ${systemState.accessPinSuspended?.at}) — resuming before certification`,
);
let resumeFailedDetail: string | null = null;
try {
const resumeResult = await executeKernelCommand({
workspaceRoot: context.workspaceRoot,
commandName: "leitstand.access.resume",
argv: [`--site=${systemId}`],
});
const resumeReports = Array.isArray(resumeResult) ? resumeResult : [resumeResult];
if (
resumeReports.some((r: { ok?: boolean; exitCode?: number }) => !r.ok || r.exitCode !== 0)
) {
resumeFailedDetail = "leitstand.access.resume reported failure";
}
} catch (err) {
resumeFailedDetail = err instanceof Error ? err.message : String(err);
}
if (resumeFailedDetail !== null) {
const msg =
`[leitstand.certify] failed to resume access PIN suspension for site '${systemId}': ${resumeFailedDetail}. ` +
`The site may still be unprotected. Run: pnpm exec werkstatt run leitstand.access.resume --site ${systemId}`;
return {
exitCode: 1,
summary: msg,
nextSteps: [{ action: msg, kind: "required" as const }],
data: {
command: "leitstand.certify",
systemId,
gate,
decisionId: "",
status: "fail",
outputPath: "",
producerCount: 0,
evidenceCount: 0,
pinManagement: {
autoManaged: autoManagePin,
suspended: false,
resumed: false,
staleResumed: false,
restoreError: resumeFailedDetail,
},
} as CertifyResult,
} as unknown as KernelCommandResult;
}
staleResumed = true;
suspensionActive = false;
} else if (suspensionActive && !autoManagePin) {
context.logger.info(
`[leitstand.certify] access-PIN suspension marker present (by ${systemState.accessPinSuspended?.by} at ${systemState.accessPinSuspended?.at}) — proceeding on the open channel; --auto-manage-pin=false leaves secrets untouched`,
);
}
const cacheCloneDir = resolveCacheClonePath(context.workspaceRoot, systemId);
const candidateServerDir = path.join(releaseDir, "dist", "server");
// Candidate mode fails closed when the built artifact is missing — the
// authorizing gate never silently measures the incumbent channel. The check
// runs before PIN management so an absent artifact does not churn the access
// PIN. Explicit --base-url and --target channel bypass it.
if (!baseUrlFlag && certifyTarget === "candidate") {
const earlyIo = context.io;
if (!(await earlyIo.exists(candidateServerDir))) {
const msg =
`candidate artifact missing: ${candidateServerDir} not found. ` +
`The gate measures the candidate artifact through a local preview (RFC-1207) — build it first.`;
return {
data: {
command: "leitstand.certify",
systemId,
gate,
decisionId: "",
status: "fail",
outputPath: "",
producerCount: 0,
evidenceCount: 0,
measurementTarget: "candidate-preview",
measuredReleaseId: releaseId,
} as CertifyResult,
summary: `[leitstand.certify] ${msg}`,
exitCode: 2,
nextSteps: [
{
action: `Build the release artifact: pnpm exec werkstatt run release.prepare --mission `,
kind: "required",
},
],
} as unknown as KernelCommandResult;
}
}
const forceRequested = flagBoolean(input, "force");
// RFC-1284: suspend only when the measurement target is a protected live
// host — a `--target channel` run, or an explicit --base-url pointing at a
// configured dev/alt channel hostname. candidate-preview (the RFC-1207
// default) never touches the channel and never suspends.
const operationId = `certify-${new Date()
.toISOString()
.toLowerCase()
.replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
const protectedLiveTarget = await isProtectedLiveTarget(
baseUrlFlag,
certifyTarget,
context.workspaceRoot,
systemId,
);
if (recordedPin !== null && autoManagePin && protectedLiveTarget) {
context.logger.info(
`[leitstand.certify] access PIN active on protected live target — suspending for certification`,
);
const suspendResult = await executeKernelCommand({
workspaceRoot: context.workspaceRoot,
commandName: "leitstand.access.suspend",
argv: [`--site=${systemId}`, `--by=leitstand.certify`, `--operation-id=${operationId}`],
});
const suspendReports = Array.isArray(suspendResult) ? suspendResult : [suspendResult];
const suspendFailed = suspendReports.some(
(r: { ok?: boolean; exitCode?: number }) => !r.ok || r.exitCode !== 0,
);
if (suspendFailed) {
const msg =
`[leitstand.certify] failed to suspend access PIN for site '${systemId}'. ` +
`Producers would hit the protected channel. ` +
`Run: pnpm exec werkstatt run leitstand.access.suspend --site ${systemId}`;
return {
exitCode: 1,
summary: msg,
nextSteps: [{ action: msg, kind: "required" as const }],
data: {
command: "leitstand.certify",
systemId,
gate,
decisionId: "",
status: "fail",
outputPath: "",
producerCount: 0,
evidenceCount: 0,
pinManagement: {
autoManaged: true,
suspended: false,
resumed: false,
staleResumed,
restoreError: null,
},
} as CertifyResult,
} as unknown as KernelCommandResult;
}
pinSuspended = true;
// Cloudflare Workers may retain the deleted secret for a few seconds.
// Wait before running discovery to avoid 401 race condition.
console.info(
`[leitstand.certify] waiting ${PIN_DELETION_PROPAGATION_DELAY_MS / 1000}s for PIN deletion to propagate…`,
);
await new Promise((resolve) => setTimeout(resolve, PIN_DELETION_PROPAGATION_DELAY_MS));
} else if (recordedPin !== null && !autoManagePin && !suspensionActive) {
// RFC-0929/RFC-1284: fail-early fires only when the pin is active and no
// suspension is in effect — a suspended system already has an open channel.
const msg =
`[leitstand.certify] site '${systemId}' has access PIN protection active. ` +
`Run: pnpm exec werkstatt run leitstand.access.suspend --site ${systemId}`;
return {
exitCode: 1,
summary: msg,
nextSteps: [{ action: msg, kind: "required" as const }],
data: {
command: "leitstand.certify",
systemId,
gate,
decisionId: "",
status: "fail",
outputPath: "",
producerCount: 0,
evidenceCount: 0,
pinManagement: {
autoManaged: false,
suspended: false,
resumed: false,
staleResumed,
restoreError: null,
},
} as CertifyResult,
} as unknown as KernelCommandResult;
}
// Inner function containing all certification logic. The try/finally wrapper
// below ensures the PIN is restored even on early returns or thrown errors.
const certifyCore = async (
io: WorkspaceIO = createDefaultIO().io,
): Promise> => {
// RFC-1207: resolve the measurement target inside certifyCore so the
// preview lifecycle stays scoped to the evaluation. Explicit --base-url
// wins verbatim; channel mode keeps the legacy deployment-record path;
// candidate mode provisions wrangler dev lazily — only when producers
// actually run (evidence reuse skips the boot entirely).
let baseUrl: string | undefined;
let measuredReleaseId: string | undefined;
let measurementTarget: "candidate-preview" | "channel" | "explicit" | undefined;
let measuredMismatch: string | null = null;
let preview: CandidatePreview | null = null;
if (baseUrlFlag) {
baseUrl = baseUrlFlag;
measurementTarget = "explicit";
} else if (certifyTarget === "channel") {
const resolvedBase = await resolveDevDeploymentRecord(cacheCloneDir, undefined);
baseUrl = resolvedBase?.deploymentUrl;
measuredReleaseId = resolvedBase?.releaseId;
measurementTarget = "channel";
// RECOVERY-2026-10-05: stale-channel detection. The gate crawls the
// latest *succeeded dev* deployment record's URL — which may host a
// different release than the candidate. Surface that upfront so a
// misleading verdict self-explains instead of being chased as a
// regression. Audit-only mode — ship never passes --target channel.
measuredMismatch = detectMeasuredReleaseMismatch(undefined, measuredReleaseId, releaseId);
if (measuredMismatch) {
context.logger.warn(
`[leitstand.certify] gate=${gate} measures the recorded dev deployment of ${measuredMismatch}, not candidate ${releaseId} — findings reflect the deployed channel artifact. ` +
`For candidate-true certification serve the release artifact locally and pass --base-url (RECOVERY-2026-10-04).`,
);
}
} else {
measurementTarget = "candidate-preview";
measuredReleaseId = releaseId;
}
const reuseEntry = await tryReuseEvidence(
cacheCloneDir,
releaseId,
artifactHash,
forceRequested,
);
// RFC-1295: producer set derives from the profile's requirements — the
// editorial-signoff producer joins mission.check for editorial-localization.
const producerNodes: ProducerDependencyNodeV1[] = [
...new Set(astroCertificationProfile.requirements.map((r) => r.producerId)),
].map((producerId) => ({ producerId, dependsOn: [] }));
const plan = planProducers(producerNodes);
if (!plan.ok) {
return {
exitCode: 1,
summary: `[leitstand.certify] producer planning failed: ${plan.ruleId} — ${plan.message}`,
nextSteps: [
{
action: `[leitstand.certify] producer planning failed: ${plan.ruleId} — ${plan.message}`,
kind: "required" as const,
},
],
data: {
command: "leitstand.certify",
systemId,
gate,
decisionId: "",
status: "incomplete",
outputPath: "",
producerCount: 0,
evidenceCount: 0,
},
diagnostics: [
{ ruleId: plan.ruleId, severity: "error", message: plan.message, evidence: [] },
],
} as unknown as KernelCommandResult;
}
const now = new Date().toISOString();
// operationId is generated at run head (RFC-1284) so the access.suspend
// marker and producer attempts share one run identity.
const candidate: ReleaseCandidateV1 = {
schema: "werkstatt/release-candidate@1",
candidateId,
systemId,
releaseVersion: releaseId,
sourceHash: artifactHash,
contentHash: artifactHash,
artifactHash,
buildConfig: {
schema: "werkstatt/build-config@1",
buildConfigHash: artifactHash,
toolchainId: "astro",
sourceRef: "src/content",
contentHash: artifactHash,
},
deploymentPlan: {
schema: "werkstatt/deployment-plan@1",
deploymentPlanHash: artifactHash,
channel: gateChannel,
target: systemId,
environmentRefs: [],
},
policyBundleRoot: artifactHash,
toolchainId: "astro",
observedEnvironment: {
schema: "werkstatt/observed-environment@1",
environment: "dev",
environmentIdentityHash: artifactHash,
observedAt: now,
},
observedAt: now,
};
const policyBundle: CertificationPolicyBundleV1 = {
schema: "werkstatt/certification-policy-bundle@1",
policyBundleId: "astro-default-bundle",
version: "1.0.0",
profileId: astroCertificationProfile.id,
resolvedRequirements: astroCertificationProfile.requirements.map((req) => ({
requirementId: req.id,
source: "astro-certification-profile",
description: req.title,
mandatory: req.classification === "required",
})),
producerManifests: Object.values(astroCertificationProfile.producers).map((p) => ({
schema: "werkstatt/producer-manifest@1",
producerId: p.id,
version: "1.0.0",
capabilityId: p.id,
schemaHash: artifactHash,
})),
rubricManifests: [],
toolchainManifests: [],
issuerManifests: [],
riskPolicy: { maxStale: 0, maxIncomplete: 0, blockOnFail: true },
retention: {
minRetentionDays: astroCertificationProfile.retentionPolicy.minRetentionDays,
maxRetentionDays: astroCertificationProfile.retentionPolicy.maxRetentionDays,
},
materializedAt: now,
};
const handler: ProducerExecutionHandlerV1 = async (
execInput: ProducerExecutionInputV1,
io: WorkspaceIO = createDefaultIO().io,
): Promise => {
const evidenceId = `ev-${execInput.producerId}-${now.toLowerCase().replace(/[:.]/g, "-")}`;
const diagnostics: Array<{
ruleId: string;
severity: "error" | "warning" | "info";
message: string;
evidence: Array<{ kind: "source" | "config" | "rule" | "rendered" | "cache" | "runtime" }>;
}> = [];
if (execInput.producerId === "astro-mission-check" && baseUrl) {
try {
const { executeKernelCommand } = await import("@warpgogol/werkstatt-engine/kernel");
let missionId: string | undefined;
try {
const buildIdentityPath = path.join(
context.workspaceRoot,
"releases",
releaseId,
"dist",
"client",
".well-known",
"build-identity.json",
);
const identity = JSON.parse(await io.readFile(buildIdentityPath));
missionId = identity.missionId;
} catch {
// build-identity.json not yet written or malformed — missionId unavailable
}
const argv = [
`--site=${systemId}`,
"--external-preview",
`--base-url=${baseUrl}`,
`--channel=${gateChannel}`,
"--no-report",
];
if (missionId) argv.push(`--mission=${missionId}`);
let timer: ReturnType | undefined;
const timeoutPromise = new Promise((_, reject) => {
timer = setTimeout(
() =>
reject(
new Error(`mission.check timed out after ${MISSION_CHECK_TIMEOUT_MS / 1000}s`),
),
MISSION_CHECK_TIMEOUT_MS,
);
});
try {
const result = await Promise.race([
executeKernelCommand({
workspaceRoot: context.workspaceRoot,
commandName: "mission.check",
argv,
}),
timeoutPromise,
]);
const reports = Array.isArray(result) ? result : [result];
const failed = reports.find((r) => !r.ok || r.exitCode !== 0);
if (failed) {
diagnostics.push({
ruleId: "MISSION-CHECK-01",
severity: "error",
message: `mission.check exited with code ${failed.exitCode}${failed.summary ? `: ${failed.summary}` : ""}`,
evidence: [],
});
} else {
diagnostics.push({
ruleId: "MISSION-CHECK-01",
severity: "info",
message: "mission.check passed",
evidence: [],
});
}
} finally {
if (timer) clearTimeout(timer);
}
} catch (err) {
diagnostics.push({
ruleId: "MISSION-CHECK-01",
severity: "error",
message: `mission.check failed: ${err instanceof Error ? err.message : String(err)}`,
evidence: [],
});
}
} else if (execInput.producerId === "astro-mission-check" && !baseUrl) {
diagnostics.push({
ruleId: "MISSION-CHECK-01",
severity: "warning",
message:
"mission.check skipped — no dev deployment URL available (first deploy or no open mission)",
evidence: [],
});
} else if (execInput.producerId === "editorial-signoff") {
// RFC-1295: human editorial sign-off is evaluated at promote-main only.
// At earlier gates the requirement defers with an info diagnostic —
// never enforced below main, never silently satisfied.
if (gate !== "main") {
diagnostics.push({
ruleId: "EDIT-00",
severity: "info",
message: "editorial sign-off is evaluated at the main gate only — deferred",
evidence: [],
});
} else {
try {
const { executeKernelCommand } = await import("@warpgogol/werkstatt-engine/kernel");
const result = await executeKernelCommand({
workspaceRoot: context.workspaceRoot,
commandName: "editorial.review.verify",
siteName: systemId,
argv: [`--release=${releaseId}`],
outputFormat: "json",
siteExplicit: true,
});
const reports = Array.isArray(result) ? result : [result];
let emitted = false;
for (const report of reports) {
const diags =
(report.data as { diagnostics?: unknown } | undefined)?.diagnostics ?? [];
for (const d of diags as Array<{
ruleId?: string;
severity?: "error" | "warning" | "info";
message?: string;
}>) {
emitted = true;
diagnostics.push({
ruleId: d.ruleId ?? "EDIT-00",
severity: d.severity ?? "error",
message: d.message ?? "editorial.review.verify diagnostic",
evidence: [],
});
}
}
const failed = reports.find((r) => !r.ok || r.exitCode !== 0);
if (failed && !emitted) {
diagnostics.push({
ruleId: "EDIT-01",
severity: "error",
message: `editorial.review.verify exited with code ${failed.exitCode}${failed.summary ? `: ${failed.summary}` : ""}`,
evidence: [],
});
} else if (!failed && !emitted) {
diagnostics.push({
ruleId: "EDIT-00",
severity: "info",
message: "editorial sign-off verified",
evidence: [],
});
}
} catch (err) {
diagnostics.push({
ruleId: "EDIT-01",
severity: "error",
message: `editorial.review.verify failed: ${err instanceof Error ? err.message : String(err)}`,
evidence: [],
});
}
}
}
return {
schema: "werkstatt/evidence-envelope@1",
evidenceId,
candidateId,
producerId: execInput.producerId,
producerAttemptId: `${operationId}-0`,
producedAt: now,
result: {
schema: "werkstatt/evidence-result@1",
producerId: execInput.producerId,
producerAttemptId: `${operationId}-0`,
diagnostics,
bindingHash: artifactHash,
applicability: {
appliesTo: execInput.profile.requirements
.filter((r) => r.producerId === execInput.producerId)
.map((r) => r.id),
scope: "site",
},
},
payloads: [],
redaction: {
schema: "werkstatt/redaction-report@1",
policyVersion: "1.0.0",
detectedSecrets: 0,
detectedPii: 0,
resolved: true,
unresolvedSecrets: 0,
unresolvedPii: 0,
},
authorityAdmission: {
schema: "werkstatt/authority-admission@1",
authoritySequence: 1,
admittedAt: now,
admittedBy: "leitstand.certify",
},
freshness: {
expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
staleAfter: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
},
};
};
let evidence: EvidenceEnvelopeV1[];
let producerCount = producerNodes.length;
if (reuseEntry) {
console.info(
`[leitstand.certify] reusing evidence from ${reuseEntry.sourceGate} gate (artifact hash match)`,
);
evidence = reuseEntry.evidence;
producerCount = 0;
} else {
// RFC-1207: candidate mode boots the preview only here — evidence-reuse
// paths never spawn wrangler dev, and PIN-failure early returns precede
// certifyCore entirely.
if (measurementTarget === "candidate-preview") {
try {
preview = await provisionCandidatePreview({
serverDir: candidateServerDir,
releaseId,
artifactHash,
logger: context.logger,
});
baseUrl = preview.baseUrl;
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return {
data: {
command: "leitstand.certify",
systemId,
gate,
decisionId: "",
status: "fail",
outputPath: "",
producerCount: 0,
evidenceCount: 0,
measurementTarget,
measuredReleaseId,
} as CertifyResult,
summary: `[leitstand.certify] candidate preview failed (infrastructure): ${msg}`,
exitCode: 2,
nextSteps: [
{
action: `Verify ${candidateServerDir} exists and wrangler is installed (pnpm exec wrangler --version), then re-run the gate`,
kind: "required",
},
],
} as unknown as KernelCommandResult;
}
}
try {
const execResult = await executeProducers(plan, handler, {
candidate,
profile: astroCertificationProfile,
policyBundle,
config: {
...DEFAULT_PRODUCER_CONFIG,
operationId,
timestamp: now,
timeoutMs: MISSION_CHECK_TIMEOUT_MS + 60_000,
maxRetries: 1,
},
});
if (!execResult.ok) {
return {
exitCode: 1,
summary: `[leitstand.certify] producer execution failed: ${execResult.ruleId} — ${execResult.message}`,
nextSteps: [
{
action: `[leitstand.certify] producer execution failed: ${execResult.ruleId} — ${execResult.message}`,
kind: "required" as const,
},
],
data: {
command: "leitstand.certify",
systemId,
gate,
decisionId: "",
status: "incomplete",
outputPath: "",
producerCount: producerNodes.length,
evidenceCount: 0,
measurementTarget,
measuredReleaseId,
} as CertifyResult,
diagnostics: [
{
ruleId: execResult.ruleId,
severity: "error",
message: execResult.message,
evidence: [],
},
],
} as unknown as KernelCommandResult;
}
evidence = execResult.evidence;
await writeEvidenceSidecar(
cacheCloneDir,
releaseId,
artifactHash,
evidence,
now,
gateChannel,
);
} finally {
// RFC-1207 (AC-7): preview teardown is finally-guaranteed — a failed
// evaluation leaves no orphan wrangler dev. A crashed teardown warns
// but does not flip the verdict.
if (preview) {
try {
await preview.stop();
} catch (stopErr) {
context.logger.warn(
`[leitstand.certify] candidate preview teardown failed: ${stopErr instanceof Error ? stopErr.message : String(stopErr)}`,
);
}
}
}
}
const evaluationResult = evaluateCertificationDecision({
candidateId,
policyBundle,
evidence,
evaluationCutSequence: 1,
authorityTime: now,
gate: gateChannel,
decidedAt: now,
});
if (!evaluationResult.ok) {
return {
exitCode: 1,
summary: `[leitstand.certify] evaluation failed: ${evaluationResult.code} — ${evaluationResult.message}`,
nextSteps: [
{
action: `[leitstand.certify] evaluation failed: ${evaluationResult.code} — ${evaluationResult.message}`,
kind: "required" as const,
},
],
data: {
command: "leitstand.certify",
systemId,
gate,
decisionId: "",
status: "incomplete",
outputPath: "",
producerCount: producerCount,
evidenceCount: evidence.length,
measurementTarget,
measuredReleaseId,
} as CertifyResult,
diagnostics: [
{
ruleId: evaluationResult.code,
severity: "error",
message: evaluationResult.message,
evidence: [],
},
],
} as unknown as KernelCommandResult;
}
const decisionId = `dec-${now.toLowerCase().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
const gateDecision: GateDecisionV1 = {
schema: "werkstatt/gate-decision@1",
decisionId,
candidateId,
policyBundleRoot: artifactHash,
gate: gateChannel,
evaluationCut: 1,
selectedEvidence: evaluationResult.selectedEvidence.map((sel) => ({
evidenceId: sel.evidenceId,
evidenceHash: sel.evidenceHash,
selectedAt: now,
})),
status: evaluationResult.status,
coverage: evaluationResult.coverage,
reasons: [
...evaluationResult.reasons,
...(measuredReleaseId
? [
measuredMismatch
? `measured-release=${measuredReleaseId} differs from candidate=${releaseId} — gate evaluated the deployed channel artifact, not the candidate`
: `measured-release=${measuredReleaseId}`,
]
: []),
],
actionPackRef: null,
decidedAt: now,
// RFC-1207 (AC-2): provenance persisted on the decision record itself —
// not only the command result — so audits can tell which artifact the
// verdict measured.
measurementTarget,
measuredReleaseId,
};
const outputDir = path.join(cacheCloneDir, "gate-decisions");
await io.mkdir(outputDir);
const outputPath = path.join(outputDir, `${releaseId}-${gate}.json`);
await writeFileIfChanged(outputPath, JSON.stringify(gateDecision, null, 2) + "\n");
// When gate=main and status=pass, also write MainVerificationDecisionV1
let mainVerificationPath: string | undefined;
if (gate === "main" && evaluationResult.status === "pass") {
const mvDecisionId = `dec-mv-${now.toLowerCase().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
const mainVerification: MainVerificationDecisionV1 = {
schema: "werkstatt/main-verification-decision@1",
decisionId: mvDecisionId,
candidateId,
policyBundleRoot: artifactHash,
gate: "main",
evaluationCut: 1,
selectedEvidence: gateDecision.selectedEvidence,
status: "pass",
coverage: evaluationResult.coverage,
reasons: gateDecision.reasons,
actionPackRef: null,
rootDossierRef: artifactHash,
priorOperationRef: null,
decidedAt: now,
};
mainVerificationPath = path.join(outputDir, `${releaseId}-main-verification.json`);
await writeFileIfChanged(
mainVerificationPath,
JSON.stringify(mainVerification, null, 2) + "\n",
);
}
const relPath = path.join("gate-decisions", `${releaseId}-${gate}.json`);
const sidecarRelPath = path.join("gate-decisions", `${releaseId}-evidence.json`);
const sidecarExists = !reuseEntry;
try {
await gitExec(cacheCloneDir, `add ${relPath}`);
if (sidecarExists) {
await gitExec(cacheCloneDir, `add ${sidecarRelPath}`);
}
if (mainVerificationPath) {
await gitExec(
cacheCloneDir,
`add ${path.join("gate-decisions", `${releaseId}-main-verification.json`)}`,
);
}
await cacheCloneCommit(
cacheCloneDir,
`gate-decisions: ${releaseId}-${gate} (${gateDecision.status})`,
);
const branch = (await gitExec(cacheCloneDir, "rev-parse --abbrev-ref HEAD")).trim();
await gitExec(cacheCloneDir, `push origin ${branch}`);
} catch {
// Best-effort commit+push — file is written, sync will propagate later
}
const r2Config = makeR2ConfigFromEnv(process.env as Record);
if (r2Config) {
try {
const adapter = createR2StorageAdapter(r2Config);
const gateDecisionBytes = Buffer.from(JSON.stringify(gateDecision, null, 2) + "\n", "utf8");
await adapter.putObject({
digest: artifactHash,
bytes: new Uint8Array(gateDecisionBytes),
mediaType: "application/json",
});
} catch (err) {
console.warn(
`[leitstand.certify] durable sync upload failed: ${err instanceof Error ? err.message : String(err)}`,
);
}
}
return {
data: {
command: "leitstand.certify",
systemId,
gate,
decisionId,
status: evaluationResult.status,
outputPath,
mainVerificationPath,
producerCount: producerCount,
evidenceCount: evidence.length,
measurementTarget,
measuredReleaseId,
// Preview identity rides every candidate-mode result — including
// evidence-reuse runs that never booted a preview — so leitstand.ship
// can journal candidatePreview.artifactHash for resume attribution.
preview:
measurementTarget === "candidate-preview"
? {
artifactHash,
...(preview ? { baseUrl: preview.baseUrl, port: preview.port } : {}),
}
: undefined,
},
summary:
`[leitstand.certify] gate=${gate} status=${evaluationResult.status} decision=${decisionId} output=${outputPath}` +
(measuredMismatch && evaluationResult.status === "fail"
? ` — WARN: gate measured the deployed ${measuredMismatch} artifact, not candidate ${releaseId}; findings may be stale-channel state. Re-certify the candidate via --base-url (RECOVERY-2026-10-04), or deploy it first and re-run.`
: ""),
exitCode: 0,
nextSteps:
evaluationResult.status === "pass"
? gate === "alt"
? [
{
action: `Promote to main: pnpm exec werkstatt run leitstand.promote --site ${systemId} --release ${releaseId}`,
kind: "optional",
},
]
: [
{
action: `Deployment pipeline complete. Close the mission: pnpm exec werkstatt run mission.close --mission `,
kind: "optional",
},
]
: [
{
action: `Review the evaluation failures in ${outputPath}, fix the issues, then re-run: pnpm exec werkstatt run leitstand.certify --release ${releaseId} --gate ${gate}`,
kind: "required",
},
],
} as unknown as KernelCommandResult;
}; // end certifyCore
let result: KernelCommandResult;
try {
result = await certifyCore();
} finally {
if (pinSuspended) {
console.info(`[leitstand.certify] resuming access PIN after certification`);
try {
const resumeResult = await executeKernelCommand({
workspaceRoot: context.workspaceRoot,
commandName: "leitstand.access.resume",
argv: [`--site=${systemId}`],
});
const resumeReports = Array.isArray(resumeResult) ? resumeResult : [resumeResult];
const resumeFailed = resumeReports.some(
(r: { ok?: boolean; exitCode?: number }) => !r.ok || r.exitCode !== 0,
);
if (resumeFailed) {
pinRestoreError = `PIN resume failed for site '${systemId}'`;
} else {
pinResumed = true;
}
} catch (err) {
pinRestoreError = err instanceof Error ? err.message : String(err);
}
}
}
// Post-finally: if PIN resume failed, override to hard fail (exitCode 1).
// The suspension marker stays written — desired state remains declared and
// the next certify/deploy/validate converges it (RFC-1284).
if (pinSuspended && pinRestoreError !== null) {
const msg =
`[leitstand.certify] CRITICAL: access PIN resume failed: ${pinRestoreError}. ` +
`Site '${systemId}' stays suspended. Run: pnpm exec werkstatt run leitstand.access.resume --site ${systemId}`;
return {
exitCode: 1,
summary: msg,
nextSteps: [{ action: msg, kind: "required" as const }],
data: {
...(result.data ?? {}),
command: "leitstand.certify",
systemId,
gate,
pinManagement: {
autoManaged: true,
suspended: true,
resumed: false,
staleResumed,
restoreError: pinRestoreError,
},
} as CertifyResult,
} as unknown as KernelCommandResult;
}
// Attach pinManagement for auto-manage paths and entry-recovery runs.
if ((autoManagePin || staleResumed) && result.data) {
(result.data as CertifyResult).pinManagement = {
autoManaged: autoManagePin,
suspended: pinSuspended,
resumed: pinResumed,
staleResumed,
restoreError: null,
};
}
return result;
}
export interface DevDeploymentRecord {
deploymentUrl: string;
releaseId?: string;
}
export async function resolveDevDeploymentRecord(
cacheCloneDir: string,
baseUrlFlag: string | undefined,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
if (baseUrlFlag) return { deploymentUrl: baseUrlFlag };
const opsDir = path.join(cacheCloneDir, "deployment-operations");
let latest: DevDeploymentRecord | undefined;
try {
const entries = await io.readdir(opsDir);
let latestAt = "";
for (const entry of entries) {
if (!entry.name.endsWith(".json")) continue;
try {
const content = await io.readFile(path.join(opsDir, entry.name));
const record = JSON.parse(content) as {
channel?: string;
state?: DeploymentOperationState;
timestamp?: string;
deploymentUrl?: string;
releaseId?: string;
};
if (record.channel === "dev" && record.state === "succeeded" && record.deploymentUrl) {
const at = record.timestamp ?? "";
if (!latest || at > latestAt) {
latestAt = at;
latest = { deploymentUrl: record.deploymentUrl, releaseId: record.releaseId };
}
}
} catch {
// Skip malformed records
}
}
} catch {
// No deployment-operations directory yet
}
return latest;
}
export async function resolveDevBaseUrl(
cacheCloneDir: string,
baseUrlFlag: string | undefined,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
return (await resolveDevDeploymentRecord(cacheCloneDir, baseUrlFlag, io))?.deploymentUrl;
}
// RFC-1284: does this run's measurement target hit a protected live host?
// Channel mode measures the recorded dev deployment — always the protected
// channel. Explicit --base-url is protected when its hostname matches a
// configured dev/alt channel URL (covers custom subdomains and
// dev-..workers.dev), with a ^(dev|alt)[.-] prefix fallback
// for hosts absent from channel config. candidate-preview never qualifies.
export async function isProtectedLiveTarget(
baseUrlFlag: string | undefined,
certifyTarget: CertifyTarget,
workspaceRoot: string,
systemId: string,
): Promise {
if (certifyTarget === "channel") return true;
if (!baseUrlFlag) return false;
let hostname: string;
try {
hostname = new URL(baseUrlFlag).hostname.toLowerCase();
} catch {
return false;
}
// Unreadable/absent config degrades to the prefix fallback — a missing
// channel list must never crash the PIN pre-flight.
let channelUrls: Array = [];
try {
const systemConfig = await readSystemConfigSmart(workspaceRoot, systemId);
channelUrls = [
systemConfig.deployment?.channels.dev.url,
systemConfig.deployment?.channels.alt.url,
];
} catch {
channelUrls = [];
}
const channelHosts = channelUrls
.map((url) => {
try {
return url ? new URL(url).hostname.toLowerCase() : null;
} catch {
return null;
}
})
.filter((h): h is string => h !== null);
if (channelHosts.includes(hostname)) return true;
return isDevOrAltHost(hostname);
}
// RECOVERY-2026-10-05: stale-channel detection. A remote certify without
// --base-url measures whatever artifact the dev channel currently serves —
// which may be an older release than the candidate (poisoned channel: the
// gate's findings then belong to the deployed artifact, not the candidate).
// Returns the measured releaseId when it differs from the candidate, else null.
export function detectMeasuredReleaseMismatch(
baseUrlFlag: string | undefined,
measuredReleaseId: string | undefined,
candidateReleaseId: string,
): string | null {
if (baseUrlFlag || !measuredReleaseId || measuredReleaseId === candidateReleaseId) {
return null;
}
return measuredReleaseId;
}