/** * The engine-level approval gate, run at the read→mutate boundary of an * application deploy (after detection + the no-changes short-circuit, before * bootstrap). It computes the typed plan, emits it, and resolves an * approve / reject / await decision through the injected surface resolver. * * Fully backward-compatible: when no gate is engaged the boundary is skipped and * the deploy proceeds exactly as before. * * The plan travels by its lane, not by one channel: a run that RETURNS the plan * (`--plan`, or gating with no resolver) carries the rendered lines on * `ApprovalGatePlanDetail.planLines`, and a run where the plan is something said * on the way to a deployment (`--auto-approve`, a resolver-backed gate) logs * them as progress. Rich contract-event emission (`plan.available` / * `approval.awaiting` / `approval.resolved`) is deferred to the emitter * migration (Brief 2 Phases 2–3) — the resolver already receives the full * typed plan. */ import { type Result } from "@fjall/generator"; import type { DeployCallbacks } from "../../types/callbacks.js"; import type { DeployOptions } from "../../types/params.js"; import type { ApprovalGate, ApprovalGatePlanDetail } from "../../types/approval.js"; import type { CfnRegistrySummaryReader } from "../../services/infrastructure/CfnRegistryService.js"; import { type DeployScope } from "./plan/assemblyDigest.js"; import type { CfnTemplateReader } from "./plan/computeDeployPlan.js"; import { type DeployPlanLoader } from "./plan/loadDeployPlan.js"; import type { DeployPlan } from "./plan/types.js"; import { type VerifyApprovalTokenResult } from "./plan/approvalToken.js"; export interface RunApprovalGateParams { /** The surface resolver. Optional — a token-resume or plan-only run needs none. */ approvalGate?: ApprovalGate; options: DeployOptions; target: string; cdkOutPath: string; detection: { stackChanges: ReadonlyMap; currentHashes: ReadonlyMap; }; /** * The RESOLVED deploy scope (exact stack subset + image-build decision), * folded into the digest so mint and verify bind identically. Must derive * from the same values the deploy loop consumes — a token minted under * `--infra-only` then refuses as `superseded` on a full apply. */ scope: DeployScope; cfnService: CfnTemplateReader; /** * Registry oracle for replacement-verdict recomputation. Optional — without * it plan rows keep raw cloudformation-diff verdicts. */ registry?: CfnRegistrySummaryReader; /** * Injected (memoised) plan supplier — applicationDeploy shares one loader * between this gate and the unconditional destruction gate so a plan-gated * deploy never diffs twice. Omitted, the gate builds its own. */ loadPlan?: DeployPlanLoader; callbacks: DeployCallbacks; abortSignal?: AbortSignal; } export type ApprovalGateOutcome = { kind: "proceed"; } | { kind: "rejected"; reason: string; planDetail?: ApprovalGatePlanDetail; } | { kind: "awaiting"; approvalToken: string; assemblyDigest: string; expiresAt: string; planDetail: ApprovalGatePlanDetail; }; type ApprovalRefusal = Extract["reason"]; /** * The canonical operator-facing wording for each token-refusal reason. * Exported so every surface that verifies an approval token outside this * gate (the domain deploy operation) refuses with byte-identical copy. */ export declare function approvalRefusalReason(reason: ApprovalRefusal): string; /** * The plan diffs synthesised templates against the LIVE stacks (resource-level), * while detection compares template hashes against the last recorded deploy. A * bare "No changes" when detection flagged changed stacks contradicts the exit-2 * / approval-token outcome, so the empty-plan line names both signals. */ export declare function renderPlanSummaryLines(plan: DeployPlan, changedStacks: readonly string[]): readonly string[]; export declare function runApprovalGate(params: RunApprovalGateParams): Promise>; export {};