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.
230 lines • 12.5 kB
TypeScript
import { VAULT_MIRROR_KEYSTORE_STILL_REQUIRED_STATEMENT, VAULT_MIRROR_VALIDITY_NOT_FRESHNESS_STATEMENT } from "../namespaces/vault.crypto.js";
import type { VaultHead, VaultRecoveryReceipt, VaultGenesis } from "../namespaces/vault.types.js";
import type { VaultEncounteredRotation, VaultEpochDecryptFailure, VaultRetainedRefsReconcileResult } from "./vault-publication.js";
import { VaultKeystore, type VaultIdentityFile } from "./vault-keystore.js";
import { type VaultMemberBundleHint, type VaultMemberRecoveryBundle } from "./vault-member-bundle.js";
import type { VaultMirrorBackend } from "./vault-mirror-backend.js";
export interface VaultChainEntry {
generation: string;
head: VaultHead;
head_sha256: string;
}
export interface VaultChainBreak {
generation: string;
reason: string;
}
export interface VaultDiscoveryResult {
repo_id: string;
genesis: VaultGenesis;
genesis_sha256: string;
/** How the genesis was authenticated. `unauthenticated_salvage` is a real, first-class outcome (protocol §5.1 vocabulary) — never silently upgraded to "trusted". */
pin_trust: "receipt" | "unauthenticated_salvage";
/** Every fully chain-verified head, genesis excluded, generation 1..N ascending. */
chain: VaultChainEntry[];
/** The newest fully-verified generation — `"0000000000000000"` (genesis) when the chain is empty. */
newest_generation: string;
newest_head_sha256: string;
/** Non-null when the walk stopped because a head FAILED verification (a broken chain, not merely "nothing further is mirrored"). Recovery falls back to `newest_generation` either way — this field is what distinguishes an honest fallback from a quiet one. */
chain_break: VaultChainBreak | null;
/**
* Every admitted `rotate_epoch` transition this KEYLESS chain walk saw,
* oldest first (D193, rev 42 — `checkChainLink`/`assertNoTransition` admit
* it structurally without ever opening an envelope; recovery's keyed
* decrypt step, below, is the only place a rotation's envelope is opened).
*/
rotations: VaultEncounteredRotation[];
}
export interface VaultDiscoverOptions {
backend: VaultMirrorBackend;
/** An explicit pin, overriding the keystore's stored receipt. */
recovery_receipt?: VaultRecoveryReceipt;
/** Consulted only when `recovery_receipt` is omitted, keyed by the genesis's OWN `repo_id` (read from the mirror first). */
keystore?: VaultKeystore;
}
/**
* List `head/`, pin-check the genesis, and walk `prev_sha256` from genesis to
* the newest generation this mirror both HOLDS and can VERIFY. A signature or
* chain-link failure at generation K stops the walk at K-1 (`chain_break`
* names why); a generation simply absent from the mirror also stops the walk
* there, with `chain_break: null` (nothing further was ever pushed here, or
* hasn't been synced yet — not evidence of corruption).
*
* A PIN MISMATCH is the one hard, no-fallback refusal (`VAULT_CREATION_CONFLICT`):
* a substituted vault must never be silently accepted at "whatever chain
* verifies", because a substituted vault's own chain verifies perfectly
* against ITS OWN (different) creator key.
*/
export declare function discoverAndVerifyChain(options: VaultDiscoverOptions): Promise<VaultDiscoveryResult>;
export interface VaultRequiredObject {
key: string;
object_id: string;
kind: "ref_state" | "retention_roots" | "wal_pack" | "checkpoint_claim_set" | "checkpoint_manifest" | "checkpoint_pack";
}
export interface VaultAbsenceAdjudication {
object_id: string;
key: string;
/** `intentionally_pruned` when a stored `prune_intent` names this object id — never corruption; `unexplained_absence` otherwise — a real loss, named rather than silently skipped. */
adjudication: "intentionally_pruned" | "unexplained_absence";
prune_intent_object_id: string | null;
}
/**
* The required object set for one generation, resolved KEYLESSLY:
* `checkpoint_claim_set` is plaintext-structured (never encrypted, protocol
* §4.7) precisely so ITS ordered pack receipts enumerate every checkpoint
* pack + manifest id without decryption — closure resolution never needs a
* key. Walks back from `head` to the nearest checkpoint-bearing ancestor (or
* genesis) collecting each head's own carriers + WAL entries along the way.
*/
export declare function closureForGeneration(backend: VaultMirrorBackend, chain: readonly VaultChainEntry[], targetGeneration: string): Promise<VaultRequiredObject[]>;
/**
* Check presence of every required object; for each ABSENT one, check the
* mirror's OWN stored `prune_intent` objects (plaintext-structured, keyless)
* for a delete-set entry naming it. Never a silent skip: every absence is
* adjudicated one way or the other, and the caller decides what to do with an
* `unexplained_absence` (typically: fall back one generation, see {@link
* recoverWithFallback}).
*/
export declare function adjudicateAbsences(backend: VaultMirrorBackend, required: readonly VaultRequiredObject[]): Promise<VaultAbsenceAdjudication[]>;
export interface VaultRecoveryReport {
repo_id: string;
genesis_sha256: string;
pin_trust: "receipt" | "unauthenticated_salvage";
/**
* The generation recovery actually landed on — may be BELOW
* `chain_verified_to_generation` when either absence fallback (keyless or
* keyed) OR — Request 4, D193-D203 rev 42 — an epoch this keystore cannot
* open (`epoch_decrypt_failure`, non-null) capped it first, whichever is
* more restrictive.
*/
recovered_generation: string;
/**
* The newest generation the PURELY STRUCTURAL chain walk verified,
* independent of any key material — this is `discoverAndVerifyChain`'s
* own `newest_generation`, restated here under fsck's own vocabulary so a
* caller can see, honestly, how far signature verification reached versus
* how far `recovered_generation` actually materialized.
*/
chain_verified_to_generation: string;
/** Non-null iff the chain walk itself broke (see {@link VaultDiscoveryResult.chain_break}). */
chain_break: VaultChainBreak | null;
/**
* Non-null iff an admitted `rotate_epoch` transition capped
* `recovered_generation` below `chain_verified_to_generation` because this
* keystore holds no envelope for the new epoch (or the envelope is absent/
* altered on this mirror) — Request 4's named epoch boundary, never a bare
* generic absence. Always `null` on {@link VaultVerifyReport} (mode
* `keyless_verify` never opens a key).
*/
epoch_decrypt_failure: VaultEpochDecryptFailure | null;
/** Every absence encountered while resolving closures, across every generation tried, oldest attempt first. */
absences: VaultAbsenceAdjudication[];
/** True iff any absence was `unexplained_absence` — the loud, un-missable flag; never buried in a nested field. */
data_loss_detected: boolean;
validity_not_freshness: typeof VAULT_MIRROR_VALIDITY_NOT_FRESHNESS_STATEMENT;
keystore_still_required: typeof VAULT_MIRROR_KEYSTORE_STILL_REQUIRED_STATEMENT;
}
export interface VaultVerifyReport extends VaultRecoveryReport {
mode: "keyless_verify";
/** Object kinds present + confirmed reachable for the recovered generation, counted (never bytes, never plaintext). */
inventory: Record<string, number>;
/**
* Member recovery-bundle sidecars found under `member-recovery-bundles/` in
* this prefix — UNVERIFIED availability hints (vault-recovery-custody):
* nothing about them is authenticated by the chain; they only tell a human
* holder "a bundle travels with this mirror, so bundle + source recovery
* code can recover it with no server." Empty when the prefix carries none.
*/
member_recovery_bundles: VaultMemberBundleHint[];
}
/**
* Discovery + chain verification + closure/absence adjudication, WITHOUT
* touching any key material — `run402 repos fsck --mirror`. Reports the
* recoverable generation and an inventory; never decrypts, never
* materializes. A genuinely keyless integrity probe (design D5) — useful as a
* CI check that never needs a secret.
*/
export declare function verifyVaultMirror(backend: VaultMirrorBackend, options?: {
recovery_receipt?: VaultRecoveryReceipt;
keystore?: VaultKeystore;
}): Promise<VaultVerifyReport>;
export interface VaultRecoverResult extends VaultRecoveryReport {
mode: "recovered";
out_dir: string;
refs: Record<string, string>;
head_target: {
kind: "symref";
ref: string;
} | {
kind: "detached";
oid: string;
};
/**
* Non-null iff this recovery decrypted via the human-member path (a member
* recovery bundle opened with the source recovery code) rather than a
* keystore — names WHICH bundle sidecar (`bundle_key`, null when supplied
* directly), which wrapper opened, and the rp_id the context was built
* with. Faithful: a keystore recovery and a bundle recovery produce the
* same repository, but the caller can always tell which happened.
*/
member_recovery: {
bundle_key: string | null;
wrapper_id: string;
rp_id_used: string;
ek_fingerprint: string;
} | null;
/**
* clone-installs-retained-refs (D2): the same `refs/r402/retain/<oid>`
* bookkeeping `restoreObjectsInto` (clone/fetch) and `fsck` install, applied
* here so a recovered bare repo's `git fsck` is silent too — a disaster
* drill should never see dangling-commit noise for tips this recovery
* itself just proved are retained. D3: a bookkeeping failure degrades to a
* `warning` on this field; recovery itself never fails on it.
*/
retained_refs: VaultRetainedRefsReconcileResult;
/**
* `out_dir` is a BARE repository (`git init --bare`) — objects/refs/HEAD
* directly in `out_dir`, no working files. This is deliberate and matches
* every other on-disk vault layout; it is named explicitly here (rather
* than left for the reader to discover via `ls`) because a bare directory
* with no working tree reads as a failed or empty recovery otherwise
* (dogfood item 3). See `next_actions` for how to get working files.
*/
layout: "bare";
/** `git clone <out_dir> <out_dir>-worktree` — materializes working files from the bare recovery above. */
next_actions: {
action: string;
command: string;
}[];
}
export interface VaultRecoverOptions {
backend: VaultMirrorBackend;
out_dir: string;
recovery_receipt?: VaultRecoveryReceipt;
keystore?: VaultKeystore;
/**
* vault-recovery-custody — the human-member path: a member recovery
* bundle (exported wrapper ciphertexts + key identity) opened with
* `source_recovery_code` substitutes for the keystore's encryption
* identity. When `source_recovery_code` is supplied WITHOUT a bundle, the
* mirror's own `member-recovery-bundles/` sidecars are tried; none found
* is the truthful `RECOVERY_BUNDLE_MISSING` refusal (a server-side
* wrapper row that was never exported is not an offline backup). The
* recovery-receipt pin is still required for trusted recovery — key
* material never substitutes for the trust anchor.
*/
member_bundle?: VaultMemberRecoveryBundle;
source_recovery_code?: string;
/** Seal-time ceremony host for the wrapper context; default = bundle's own `rp_id`, then `console.run402.com`. */
rp_id?: string;
}
/** The two identity fields recovery actually decrypts with — a keystore's `identity.json` or a member bundle opened by the source recovery code both project onto this. */
export type VaultRecoveryIdentity = Pick<VaultIdentityFile, "encryption_private_key_hex" | "encryption_fingerprint">;
/**
* Materialize the resolved generation into `out_dir` and run §4.7 restorer
* acceptance: scratch restore, every covered ref at its EXACT oid, `git fsck`
* full connectivity, recompute the three keyed commitments against the
* checkpoint manifest when one covers this generation. Acceptance failure is
* a typed non-zero exit (throws) — never a partial, silently-accepted repo.
*/
export declare function recoverVaultMirror(options: VaultRecoverOptions): Promise<VaultRecoverResult>;
//# sourceMappingURL=vault-recover.d.ts.map