run402-mcp
Version:
MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.
265 lines • 12.4 kB
TypeScript
/**
* vault — vault-gated deploy (protocol rev 41 §6.5; task 5.6, lane supplied
* by change `vault-deploy-lane`).
*
* Every deploy has a commit, and the artifacts it ships CORRESPOND to it:
* 1. snapshot (§6.6) → the `vault_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 { VaultActivationToken, VaultCaptureReceipt, VaultHeadTarget } from "../namespaces/vault.types.js";
import { VaultKeystore } from "./vault-keystore.js";
import { Vault } from "./vault-publication.js";
import { type VaultCapturedSetDrift, type VaultSnapshot, type VaultSnapshotOptions } from "./vault-snapshot.js";
export declare const VAULT_DEPLOY_OUTCOMES: readonly ["DEPLOYED_AND_VAULTED", "DEPLOY_BLOCKED_PUSH_FAILED", "DEPLOY_FAILED_VAULTED", "DEPLOY_FAILED_UNVAULTED", "DEPLOYED_UNVAULTED_OVERRIDE"];
export type VaultDeployOutcome = (typeof VAULT_DEPLOY_OUTCOMES)[number];
/** A serializable error summary (never plaintext, never presigned URLs). */
export interface VaultDeployError {
code: string;
message: string;
retryable: boolean;
details?: unknown;
}
export interface VaultDeployNextAction {
action: string;
/** Present when the action needs a capability the caller may lack. */
requires?: string;
}
interface VaultDeployResultBase {
outcome: VaultDeployOutcome;
/** The snapshot commit — ALWAYS present; the `vault_commit` line is derived from it. */
vault_commit: string;
vault_commit_line: string;
snapshot: VaultSnapshot;
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: VaultDeployNextAction[];
}
export type VaultDeployResult = (VaultDeployResultBase & {
outcome: "DEPLOYED_AND_VAULTED";
generation: string;
head_sha256: string;
capture_receipt: VaultCaptureReceipt;
activation_token: VaultActivationToken;
commit: unknown;
}) | (VaultDeployResultBase & {
outcome: "DEPLOY_BLOCKED_PUSH_FAILED";
push_error: VaultDeployError;
previous_release_keeps_serving: true;
}) | (VaultDeployResultBase & {
outcome: "DEPLOY_FAILED_VAULTED";
generation: string;
head_sha256: string;
capture_receipt: VaultCaptureReceipt | null;
deploy_error: VaultDeployError;
}) | (VaultDeployResultBase & {
outcome: "DEPLOY_FAILED_UNVAULTED";
push_error: VaultDeployError;
deploy_error: VaultDeployError;
}) | (VaultDeployResultBase & {
outcome: "DEPLOYED_UNVAULTED_OVERRIDE";
push_error: VaultDeployError;
override_journal: VaultOverrideJournal;
commit: unknown;
});
export interface VaultDeployLanePlanInput {
/**
* 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 VaultDeployLanePlan {
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 VaultDeployLaneCommitInput {
plan_id: string;
operation_id: string;
/** Present on the vaulted path. */
activation_token?: VaultActivationToken;
/** 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 VaultDeployLane {
plan(input: VaultDeployLanePlanInput): Promise<VaultDeployLanePlan>;
commit(input: VaultDeployLaneCommitInput): Promise<unknown>;
}
export interface VaultOverrideJournal {
version: 1;
repo_id: string;
operation_id: string;
capture_id: string;
apply_plan_sha256: string;
snapshot: {
ref: string;
oid: string;
head_target: VaultHeadTarget;
};
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: VaultCaptureReceipt | null;
reason: string;
created_at: string;
updated_at: string;
}
export declare function overrideJournalDir(keystore: VaultKeystore): string;
export declare function overrideJournalPath(keystore: VaultKeystore, operationId: string): string;
export declare function writeOverrideJournal(keystore: VaultKeystore, journal: VaultOverrideJournal): void;
export declare function readOverrideJournal(keystore: VaultKeystore, operationId: string): VaultOverrideJournal | null;
/** Every override journal not yet `completed` — the doctor-visible advisory list. */
export declare function listPendingOverrideJournals(keystore: VaultKeystore, repoId?: string): VaultOverrideJournal[];
export interface VaultCaptureReceiptMatch {
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: VaultCaptureReceipt, expected: {
repo_id: string;
capture_id: string;
apply_plan_sha256: string | null;
snapshot_oid_hmac: string;
head_sha256: string;
}): VaultCaptureReceiptMatch;
/**
* `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: VaultActivationToken, 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 VaultDeployOptions {
vault: Vault;
lane: VaultDeployLane;
/** The work tree to snapshot. */
repo_dir: string;
snapshot?: Omit<VaultSnapshotOptions, "dir">;
/** 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 `vault_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 (`repos.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 VaultSnapshotMovedDetails extends VaultCapturedSetDrift {
vault_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 vault-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 runVaultDeploy(options: VaultDeployOptions): Promise<VaultDeployResult>;
export interface VaultOverrideDrainReport {
completed: VaultOverrideJournal[];
/** Still pending, with the reason the drain could not complete them this time. */
remaining: Array<{
journal: VaultOverrideJournal;
error: VaultDeployError;
}>;
}
/**
* 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: Vault, options?: {
now?: () => Date;
}): Promise<VaultOverrideDrainReport>;
export {};
//# sourceMappingURL=vault-deploy.d.ts.map