/** * repo-merge-gate (#7) — the PURE gate decision (repo-merge-gate-SPEC §1). Placement B: the repo-side * required-status-check logic that answers "may THIS PR head merge, given the finished receipt and the * observed branch-protection state?" * * This is a PURE function — it performs NO I/O and makes NO GitHub call (branch protection is an * INPUT, observed by the host and passed in). It does NOT re-decide: it reads only the finished * receipt fields (decision / lifecycle authz / binding) — the deterministic verdict stays the * server's (M5). Same inputs → same output (M8). * * Scope honesty is the whole point (§4): `inescapable_merge` is true ONLY when proof is complete * (ENFORCING ∧ ¬admin_bypass ∧ App-bound required check ∧ fingerprint rebound). Missing any * conjunct → FALSE (fail-closed). The check may still be `success` for visibility. * * Public source (ships in the npm package): it references only receipt/protection fields and generic * merge-gate concepts — no scoring logic, weights, thresholds, pattern names, endpoints, or secrets. */ export type GateStatusState = 'success' | 'failure' | 'pending'; /** Gate reason codes (§1.1 + honesty residuals). Success uses allow_current_head; * protection_* / admin_bypass_open / required_check_app_* / change_set_not_rebound name honesty * residuals on an otherwise-green check; fingerprint_mismatch is a hard failure (not a residual); * server_unreachable is a host-supplied fail-closed policy input. */ export type GateReason = 'allow_current_head' | 'no_receipt' | 'receipt_not_authorized' | 'stale_head' | 'fingerprint_mismatch' | 'operation_mismatch' | 'target_mismatch' | 'decision_not_allow' | 'body_hash_mismatch' | 'protection_not_configured' | 'protection_advisory_only' | 'admin_bypass_open' | 'inputs_incomplete' | 'server_unreachable' /** Host did not report whether the required check is app-bound (field absent). Not the same as not bound. */ | 'required_check_app_binding_unknown' /** Host reported the required check is not bound to a GitHub App (name-only / spoofable). */ | 'required_check_app_not_bound' /** * Host did not supply expected_fingerprint — the change-set re-bind was skipped. * Residual names the gap; inescapable_merge is false (fail-closed — cannot assert * inescapable without a rebound fingerprint). Distinct from fingerprint_mismatch * (hard failure when a re-bind WAS requested and disagreed). merge_allowed may stay true. */ | 'change_set_not_rebound'; export type EnforcementState = 'ENFORCING' | 'ADVISORY' | 'UNKNOWN' | 'ABSENT'; /** A view of the finished receipt (fields already produced by the server + lifecycle evaluation). * Reuses the shipped field names — the gate never re-derives any of them. */ export type ReceiptView = { /** Lifecycle authorization result (NOT signature validity alone). */ currently_authorized: boolean; authz_reason?: string; decision: 'ALLOW' | 'WARN' | 'REQUIRE_APPROVAL' | 'BLOCK' | string; execution_action?: 'CONTINUE' | 'CONTINUE_WITH_MONITORING' | 'REQUEST_APPROVAL' | 'STOP' | string; /** The PR head commit this receipt was issued for (required for a non-null receipt). */ bound_head_sha: string; verdict_fingerprint?: string; body_hash?: string; operation?: 'merge' | 'deploy' | 'tool_call' | 'publish' | string; target_id?: string; environment?: string; /** Structural signature ok — NOT sufficient alone (currently_authorized is the gate). */ signature_valid?: boolean; }; /** Branch-protection observation (from the host's GitHub read — the pure gate consumes it as input). */ export type ProtectionState = { enforcement: EnforcementState; admin_bypass_possible: boolean; required_context_name?: string; /** * Whether the required status check is bound to a specific GitHub App (`app_id` set on the * protection rule). OPTIONAL: omit when the host has not observed this — absence means * **unknown**, not "not bound". `true` = app-bound (not name-spoofable); `false` = known * unbound (context name only; write access can satisfy the check). */ required_check_app_bound?: boolean; }; export type RequiredContext = { operation?: string; repository?: string; expected_fingerprint?: string; expected_body_hash?: string; protection: ProtectionState; allowWarnMerge?: boolean; allowPrefixCompare?: boolean; allowPending?: boolean; }; export type GateDecisionInput = { prHeadSha: string; receipt: ReceiptView | null; requiredContext: RequiredContext; /** Incomplete/no-receipt → pending when true, else failure (default false). */ allowPending?: boolean; /** Green the gate on WARN as well as ALLOW (default false). */ allowWarnMerge?: boolean; /** Compare head SHAs by prefix instead of full 40-char equality (default false). */ allowPrefixCompare?: boolean; }; export type GateDecision = { merge_allowed: boolean; state: GateStatusState; reason: GateReason; enforcement_state: EnforcementState; inescapable_merge: boolean; /** * Named honesty residual on an otherwise-green check (§4.2). Usually present when * inescapable_merge is false; may also name an app-binding gap while inescapable_merge * remains true (published callers cannot supply that fact yet — residual only, no flip). * * Compat: always the FIRST residual in evaluation order (same priority as pre-array * single-slot assembly). Prefer `residuals` when co-occurrence matters. */ residual?: GateReason; /** * ALL applicable honesty residuals in evaluation order (no duplicates). * Empty array when none. Additive — singular `residual` remains for published callers. */ residuals: GateReason[]; detail: { prHeadSha: string; bound_head_sha: string | null; decision: string | null; }; }; /** * §1.4 — the normative, first-match-wins gate decision. Pure; no I/O; deterministic. */ export declare function gateDecision(input: GateDecisionInput): GateDecision; //# sourceMappingURL=merge-gate.d.ts.map