/** * gitvault — push-gated deploy (protocol rev 41 §6.5; task 5.6, lane supplied * by change `gitvault-deploy-lane`). * * Every deploy has a commit, and the artifacts it ships CORRESPOND to it: * 1. snapshot (§6.6) → the `gitvault_commit`, printed ALWAYS, never sent, * plus a digest over the captured file set; * 2. a fresh `capture_id` before BOTH lanes; * 3. the deploy lane collects artifacts from the work tree and produces the * canonical plan digest (`apply_plan_sha256`, null when the build fails * before a plan exists) — concurrently, the push lane prepares the * publication; * 4. the push's head carries `capture_binding {capture_id, apply_plan_sha256|null, * snapshot_oid_hmac}` — capture-the-attempt survives a failed build; no * token is mintable from a null digest; * 5. BEFORE any plan commits, the captured-set digest is RE-DERIVED. A * difference is `SNAPSHOT_MOVED_DURING_DEPLOY`: the deploy stops, naming * the changed paths, and commits nothing; * 6. with a digest and an admitted head, the capture receipt is exchanged * for an activation token, and the apply commit is submitted ONLY with it; * 7. the outcome is one of the CLOSED five: `DEPLOYED_AND_VAULTED`, * `DEPLOY_BLOCKED_PUSH_FAILED`, `DEPLOY_FAILED_VAULTED`, * `DEPLOY_FAILED_UNVAULTED`, `DEPLOYED_UNVAULTED_OVERRIDE`. * * WHAT CORRESPONDENCE MEANS, AND WHAT IT DOES NOT. Step 5 establishes that the * captured source did NOT change while the artifacts were produced. It does * NOT establish that the artifacts are a reproducible function of that source: * nothing here ties a gitignored `dist/` to the `src/` it came from, and the * captured set deliberately excludes gitignored paths so a project with a * build step stays deployable. The stronger property — build inside an * isolated materialization, making artifacts a function of the snapshot — is a * recorded forward TODO, not a thing this module quietly delivers. * * REFUSE, NEVER REPAIR. On a detected move the deploy stops rather than * re-capturing: a second capture publishes a generation whose relationship to * the artifacts already collected is exactly what is in doubt, and a * capture→collect→capture loop churns generations. The caller re-runs. * * The push is never gated on deploy success. The unvaulted override * (`allow_unvaulted`) is journaled crash-safely BEFORE the commit with the * full §6.5 contents; any later invocation drains the journal by pushing the * journaled snapshot and presenting the capture receipt to the * override-completion route — equality on EVERY field or no clear. */ import type { GitvaultActivationToken, GitvaultCaptureReceipt, GitvaultHeadTarget } from "../namespaces/gitvault.types.js"; import { GitvaultKeystore } from "./gitvault-keystore.js"; import { GitvaultVault } from "./gitvault-publication.js"; import { type GitvaultCapturedSetDrift, type GitvaultSnapshot, type GitvaultSnapshotOptions } from "./gitvault-snapshot.js"; export declare const GITVAULT_DEPLOY_OUTCOMES: readonly ["DEPLOYED_AND_VAULTED", "DEPLOY_BLOCKED_PUSH_FAILED", "DEPLOY_FAILED_VAULTED", "DEPLOY_FAILED_UNVAULTED", "DEPLOYED_UNVAULTED_OVERRIDE"]; export type GitvaultDeployOutcome = (typeof GITVAULT_DEPLOY_OUTCOMES)[number]; /** A serializable error summary (never plaintext, never presigned URLs). */ export interface GitvaultDeployError { code: string; message: string; retryable: boolean; details?: unknown; } export interface GitvaultDeployNextAction { action: string; /** Present when the action needs a capability the caller may lack. */ requires?: string; } interface GitvaultDeployResultBase { outcome: GitvaultDeployOutcome; /** The snapshot commit — ALWAYS present; the `gitvault_commit` line is derived from it. */ gitvault_commit: string; gitvault_commit_line: string; snapshot: GitvaultSnapshot; capture_id: string; snapshot_oid_hmac: string; /** The canonical plan digest — null when the build failed before a plan existed. */ apply_plan_sha256: string | null; operation_id: string | null; next_actions: GitvaultDeployNextAction[]; } export type GitvaultDeployResult = (GitvaultDeployResultBase & { outcome: "DEPLOYED_AND_VAULTED"; generation: string; head_sha256: string; capture_receipt: GitvaultCaptureReceipt; activation_token: GitvaultActivationToken; commit: unknown; }) | (GitvaultDeployResultBase & { outcome: "DEPLOY_BLOCKED_PUSH_FAILED"; push_error: GitvaultDeployError; previous_release_keeps_serving: true; }) | (GitvaultDeployResultBase & { outcome: "DEPLOY_FAILED_VAULTED"; generation: string; head_sha256: string; capture_receipt: GitvaultCaptureReceipt | null; deploy_error: GitvaultDeployError; }) | (GitvaultDeployResultBase & { outcome: "DEPLOY_FAILED_UNVAULTED"; push_error: GitvaultDeployError; deploy_error: GitvaultDeployError; }) | (GitvaultDeployResultBase & { outcome: "DEPLOYED_UNVAULTED_OVERRIDE"; push_error: GitvaultDeployError; override_journal: GitvaultOverrideJournal; commit: unknown; }); export interface GitvaultDeployLanePlanInput { /** * The work tree the snapshot was captured from — collect artifacts from * HERE. It is the mutable tree on purpose (a materialization of the snapshot * commit holds source and no build output, because build output is * gitignored). Correspondence is enforced by re-deriving the captured-set * digest before the plan commits, not by isolating this directory. */ source_dir: string; capture_id: string; snapshot_oid_hmac: string; } export interface GitvaultDeployLanePlan { plan_id: string; /** The apply operation the token is minted for (the gateway creates it at plan time under the vault gate). */ operation_id: string; /** The canonical `apply_plan_canonical/v1` digest (opaque 32-byte commitment, hex). */ apply_plan_sha256: string; } export interface GitvaultDeployLaneCommitInput { plan_id: string; operation_id: string; /** Present on the vaulted path. */ activation_token?: GitvaultActivationToken; /** Present on the override path — requires `gitvault.override_unvaulted` server-side. */ allow_unvaulted?: { reason: string; }; } /** The apply-v1 plan → commit pair, abstracted so the deploy logic is testable without a gateway. */ export interface GitvaultDeployLane { plan(input: GitvaultDeployLanePlanInput): Promise; commit(input: GitvaultDeployLaneCommitInput): Promise; } export interface GitvaultOverrideJournal { version: 1; repo_id: string; operation_id: string; capture_id: string; apply_plan_sha256: string; snapshot: { ref: string; oid: string; head_target: GitvaultHeadTarget; }; snapshot_oid_hmac: string; /** `pending` → committed unvaulted, not yet pushed; `published` → pushed, completion not yet accepted; `completed` → advisory cleared server-side. */ publication_state: "pending" | "published" | "completed"; generation: string | null; head_sha256: string | null; capture_receipt: GitvaultCaptureReceipt | null; reason: string; created_at: string; updated_at: string; } export declare function overrideJournalDir(keystore: GitvaultKeystore): string; export declare function overrideJournalPath(keystore: GitvaultKeystore, operationId: string): string; export declare function writeOverrideJournal(keystore: GitvaultKeystore, journal: GitvaultOverrideJournal): void; export declare function readOverrideJournal(keystore: GitvaultKeystore, operationId: string): GitvaultOverrideJournal | null; /** Every override journal not yet `completed` — the doctor-visible advisory list. */ export declare function listPendingOverrideJournals(keystore: GitvaultKeystore, repoId?: string): GitvaultOverrideJournal[]; export interface GitvaultCaptureReceiptMatch { equal: boolean; mismatched_fields: string[]; } /** Compare a capture receipt against an operation's journaled binding on EVERY field: repo, capture_id, plan digest, snapshot commitment, head hash. */ export declare function matchCaptureReceipt(receipt: GitvaultCaptureReceipt, expected: { repo_id: string; capture_id: string; apply_plan_sha256: string | null; snapshot_oid_hmac: string; head_sha256: string; }): GitvaultCaptureReceiptMatch; /** * `authorization_epoch` is compared BYTEWISE against the epoch the client last * saw installed (a DR gate mints a fresh one). Any difference — one nibble is * enough — is `AUTHORIZATION_EPOCH_STALE`, never a retry under the old epoch. */ export declare function checkAuthorizationEpoch(tokenEpoch: string, installedEpoch: string): void; /** Verify a minted activation token binds exactly this deploy (before it is ever presented to a commit). */ export declare function checkActivationTokenBinding(token: GitvaultActivationToken, expected: { repo_id: string; operation_id: string; generation: string; head_sha256: string; capture_id: string; apply_plan_sha256: string; snapshot_oid_hmac: string; }): string[]; export interface GitvaultDeployOptions { vault: GitvaultVault; lane: GitvaultDeployLane; /** The work tree to snapshot. */ repo_dir: string; snapshot?: Omit; /** The unvaulted override — requires `gitvault.override_unvaulted` server-side; refused otherwise. */ allow_unvaulted?: { reason: string; }; /** Test/replay hook: pin the capture id. */ capture_id?: string; now?: () => Date; /** Called with the `gitvault_commit` line as soon as the snapshot exists — the CLI prints it. */ onCommitLine?: (line: string) => void; } /** * D193's `EPOCH_ROTATION_REQUIRED` names WHICH of three independent causes * is outstanding via boolean flags (`migration_required` / * `revocation_outstanding` / `exposure_outstanding` — any subset, never * mutually exclusive); this decodes them into the exact `repos access` * command that remedies each one. Shared with the namespace-level `push()` * enrichment (`gitvault.ts#enrichEpochRotationRequired`) — same three * causes, same three commands, one place either could drift from. */ export declare function epochRotationRequiredNextActions(e: unknown): { action: string; why: string; }[]; /** * The correspondence refusal. CLIENT-LOCAL by design: the moved tree is * detected before any plan is * committed, so this code never crosses the wire and is deliberately absent * from the protocol's frozen §11 wire registry. The accepted cost is * discoverability — which is why it is named here, in the client's own error * surface, rather than left to be found by grep. */ export declare const SNAPSHOT_MOVED_DURING_DEPLOY = "SNAPSHOT_MOVED_DURING_DEPLOY"; /** What `SNAPSHOT_MOVED_DURING_DEPLOY` carries in `details`. */ export interface GitvaultSnapshotMovedDetails extends GitvaultCapturedSetDrift { gitvault_commit: string; capture_id: string; captured_digest: string; observed_digest: string; /** Present when the snapshot reached the vault before the tree moved. */ generation?: string; } /** * Run the push-gated deploy. Throws ONLY for refusals that precede any lane * (snapshot refusals such as `SNAPSHOT_CONFLICTED_INDEX`, unsupported * repositories), for `SNAPSHOT_MOVED_DURING_DEPLOY` (the tree moved under the * capture; nothing is committed), and for `OVERRIDE_NOT_AUTHORIZED`; every * other path resolves to one of the five outcomes. */ export declare function runGitvaultDeploy(options: GitvaultDeployOptions): Promise; export interface GitvaultOverrideDrainReport { completed: GitvaultOverrideJournal[]; /** Still pending, with the reason the drain could not complete them this time. */ remaining: Array<{ journal: GitvaultOverrideJournal; error: GitvaultDeployError; }>; } /** * Drain every pending override journal for this vault: push the journaled * snapshot (same capture binding), verify the receipt against EVERY journaled * field + the read-back head hash, present it for completion. Partial matches * never clear; the advisory persists until equality. */ export declare function drainOverrideJournals(vault: GitvaultVault, options?: { now?: () => Date; }): Promise; export {}; //# sourceMappingURL=gitvault-deploy.d.ts.map