/** * deploy-gate (#8) — the PURE deploy authorization decision (deploy-gate-SPEC §1). The DEPLOY sibling * of the merge gate: it answers "may THIS artifact deploy to THIS environment, given the finished * receipt and the observed pipeline-enforcement state?" * * Same shape as the merge gate, different operation. It is a PURE function — NO I/O, NO CD-API call * (pipeline enforcement is an INPUT, observed by the host and passed in). TOKEN mode verifies a * chain-receipt locally (Ed25519 + registry/pinned PEM; no HTTP). VERIFIED-VIEW mode accepts a * host-attributed view ONLY with the guard-defined provenance marker. A bare * `currently_authorized: true` is not proof (P0 / external audit 2026-08-24). Same * inputs → same output (D9). Observation `verification.{mode,verify_status}` is recorded on the * outcome; it does not enter any fingerprint preimage. * * The three deploy-specific binds are the whole point: T7 (a MERGE — or operation-less — receipt can * never deploy), environment binding (a staging ALLOW never authorizes production), and artifact * binding (an old artifact's ALLOW never deploys a newer one). Scope honesty follows the merge gate's * SHAPE (a conjunction, fail-closed on any gap) but NOT its conjuncts — see below. * * `inescapable_deploy` is true ONLY when every conjunct holds: ENFORCING ∧ ¬bypass_possible ∧ * fingerprint rebound. Missing any conjunct → FALSE (fail-closed); the check may still be `success` * for visibility, with the residual named. * * WHAT THAT DOES NOT SAY, stated because the field name is stronger than the fact. The first two * conjuncts are the HOST's observation of its own CD config (`DeployRequiredContext.enforcement`), * consumed by this pure gate, not verified by it — so `inescapable_deploy: true` reports that the * host said its pipeline enforces and cannot be bypassed, plus a fingerprint we did rebind. Note * also that this gate carries NO issuer-binding conjunct at all, unlike `inescapable_merge`: where a * provider check supplies the enforcing signal, binding it to an App id proves the check came from * that provider's Actions app, NOT that CodeRifts produced it — anyone who can edit the workflow * definition can post under the same issuer. Closing that needs workflow-SHA pinning. * * Public source (ships in the npm package): references only receipt/target/enforcement fields and * generic deploy-gate concepts — no scoring logic, weights, thresholds, pattern names, endpoints, or * secrets. */ import type { GateStatusState, EnforcementState } from './merge-gate.js'; import { type DeployTokenReceipt } from './deploy-receipt-token.js'; export type { GateStatusState, EnforcementState } from './merge-gate.js'; export type { DeployTokenReceipt } from './deploy-receipt-token.js'; import { type NextAgentStep } from './next-agent-step.js'; export type { NextAgentStep } from './next-agent-step.js'; /** * Guard-defined VERIFIED-VIEW provenance marker (same idiom as `proof_spec` / * `attestation_spec`). A host that already verified (CLI, etc.) MUST set this — * `currently_authorized: true` alone is untrusted input. */ export declare const DEPLOY_RECEIPT_VIEW_SPEC: "deploy-receipt-view.v1"; /** Deploy-gate reason codes (§1.1 + change-set residual + 9.0.0 verification). Success uses allow_current_deploy; * enforcement_not_configured / bypass_open / change_set_not_rebound name honesty residuals on an * otherwise-green check; fingerprint_mismatch is a hard failure (not a residual). */ export type DeployGateReason = 'allow_current_deploy' | 'no_receipt' | 'receipt_not_authorized' | 'operation_mismatch' | 'env_mismatch' | 'stale_artifact' | 'fingerprint_mismatch' | 'body_hash_mismatch' | 'target_mismatch' | 'decision_not_allow' | 'inputs_incomplete' | 'enforcement_not_configured' | 'bypass_open' /** * Host did not supply expected_fingerprint — the change-set re-bind was skipped. * Residual names the gap; inescapable_deploy is false (fail-closed). Distinct from * fingerprint_mismatch (hard failure when a re-bind WAS requested and disagreed). * deploy_allowed may stay true. */ | 'change_set_not_rebound' /** * Caller handed a receipt view with currently_authorized (or bounds) but no TOKEN and no * guard-defined view_spec provenance. Distinct from receipt_not_authorized: "you didn't prove * it" ≠ "verification says no". */ | 'unverified_receipt_view' | 'invalid_signature' | 'expired' | 'unknown_key' | 'retired_key' /** * The registry has withdrawn this key: status `revoked`, or a `revoked_at` * timestamp on the entry. Distinct from retired_key, which is a planned * rotation whose pre-rotation receipts stay meaningful. NOT repairable by * re-requesting a receipt — the same key would sign it. */ | 'revoked_key' /** * The registry carries a key status this verifier does not understand. Fails * closed rather than reading it as healthy: an unrecognised status is more * likely a withdrawal we cannot parse than a permission we should grant. */ | 'unknown_key_status'; /** What is being deployed — all fields are INPUTS (the pure function performs no discovery). */ export type DeployTarget = { environment: string; /** Immutable artifact identity: content digest, or a commit SHA when that is the deploy unit. */ artifact_id: string; service?: string; pipeline_run_id?: string; }; /** A view of the finished receipt. Reuses the shipped field names + the deploy analogs of bound_head_sha. */ export type DeployReceiptView = { currently_authorized: boolean; authz_reason?: string; decision: 'ALLOW' | 'WARN' | 'REQUIRE_APPROVAL' | 'BLOCK' | string; execution_action?: 'CONTINUE' | 'CONTINUE_WITH_MONITORING' | 'REQUEST_APPROVAL' | 'STOP' | string; /** MUST be 'deploy' for success (T7). */ operation?: 'merge' | 'deploy' | 'tool_call' | 'publish' | string; /** The environment this receipt authorizes (deploy analog of bound_head_sha). */ bound_environment?: string | null; /** The artifact/commit this receipt was issued for (deploy analog of bound_head_sha). */ bound_artifact_id?: string | null; verdict_fingerprint?: string; body_hash?: string; target_id?: string; signature_valid?: boolean; /** * VERIFIED-VIEW provenance. Must equal DEPLOY_RECEIPT_VIEW_SPEC together with * `verified: true` or the gate denies unverified_receipt_view. */ view_spec?: string; verified?: boolean; verify_status?: string; }; /** VERIFIED-VIEW: host-attributed, guard-defined marker required. */ export type VerifiedDeployReceiptView = DeployReceiptView & { view_spec: typeof DEPLOY_RECEIPT_VIEW_SPEC; verified: true; verify_status: string; }; export type DeployVerificationMode = 'token' | 'verified_view' | 'unverified' | 'none'; export type DeployVerificationObservation = { mode: DeployVerificationMode; verify_status: string | null; }; /** Stamp a computed view so deployGate will accept it as VERIFIED-VIEW (not TOKEN). */ export declare function asVerifiedDeployReceiptView(view: DeployReceiptView, verify_status?: string): VerifiedDeployReceiptView; export declare function isVerifiedDeployReceiptView(r: DeployReceiptView | null | undefined): r is VerifiedDeployReceiptView; /** Pipeline-enforcement observation (from the host's CD-config read — the pure gate consumes it). */ export type DeployEnforcementState = { enforcement: EnforcementState; bypass_possible: boolean; required_step_name?: string; }; export type DeployRequiredContext = { operation?: 'deploy' | string; repository?: string; service?: string; expected_fingerprint?: string; expected_body_hash?: string; enforcement: DeployEnforcementState; /** Default true: a missing bound_environment on the receipt → env_mismatch. */ require_bound_environment?: boolean; /** Default true: a missing bound_artifact_id → stale_artifact. */ require_bound_artifact?: boolean; allowWarnDeploy?: boolean; allowPrefixCompare?: boolean; allowPending?: boolean; }; export type DeployGateInput = { deployTarget: DeployTarget; /** * VERIFIED-VIEW (or null). A bare currently_authorized boolean is untrusted. * TOKEN mode uses `token` instead (recommended). */ receipt?: DeployReceiptView | VerifiedDeployReceiptView | null; /** TOKEN mode: signed chain_receipt + registry | pinnedKeyPem. Guard verifies locally. */ token?: DeployTokenReceipt; requiredContext: DeployRequiredContext; /** Incomplete/no-receipt → pending when true, else failure (default false). */ allowPending?: boolean; /** Green the gate on WARN as well as ALLOW (default false; stricter for prod-shaped envs). */ allowWarnDeploy?: boolean; /** Compare env/artifact by prefix instead of full equality (default false). */ allowPrefixCompare?: boolean; }; export type DeployGateDecision = { deploy_allowed: boolean; state: GateStatusState; reason: DeployGateReason; enforcement_state: EnforcementState; inescapable_deploy: boolean; /** * Named residual when the check is green but NOT inescapable (honesty channel, §3.2). * Compat: FIRST residual in evaluation order (same priority as pre-array single-slot). * Prefer `residuals` when co-occurrence matters. */ residual?: DeployGateReason; /** * ALL applicable honesty residuals in evaluation order (no duplicates). * Empty array when none. Additive — singular `residual` remains for published callers. */ residuals: DeployGateReason[]; detail: { environment: string; artifact_id: string; bound_environment: string | null; bound_artifact_id: string | null; operation: string | null; }; /** * Observation: which input mode ran and the verify_status. Not a preimage field. */ verification: DeployVerificationObservation; /** * I-1288f — the DECISION's own remediation suggestion, lifted verbatim from the * `decision_result` envelope this gate verified. * * PRESENT ONLY IN TOKEN MODE. `verified_view` and `unverified` receipts are * host-supplied views: nothing in this process checked a signature over their * contents, so a step read from one would be guidance with no issuer behind it. * TOKEN mode is the only path where the guard itself bound the body hash. * * Absent when the envelope carried none, on every allow-class deploy (the issuer * sends null), and on every non-token mode. Additive: not a verdict input, not a * preimage field, and no branch below reads it — see the deep-equal test. * * NOT PERMISSION: branch on `reason` / `deploy_allowed`, never on this. */ next_step?: NextAgentStep; }; /** * §1.4 — the normative, first-match-wins deploy decision. Pure; no I/O; deterministic. */ export declare function deployGate(input: DeployGateInput): DeployGateDecision; //# sourceMappingURL=deploy-gate.d.ts.map