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.
202 lines • 10.7 kB
TypeScript
/**
* vault D6 — named addressing resolution + id-pinning (repo-first-onramp
* task 4.5).
*
* Two things live here, both Node-only (local git state):
*
* - PIN read/write: `r402.repoId` (+ `r402.repoAddress`, the human name it
* was resolved from) in the repository's LOCAL git config
* (`git config --local` — this checkout's own `.git/config`, never
* global). "Local git state" per design D6's id-pinning paragraph.
* - `resolveVaultAddress`: the orchestrator the remote helper and
* `vault snapshot` drive — pinned id first, then dispatch on the
* address's form (id vs slug — {@link vaultRemoteAddressForm}), then
* (slug-form only, opt-in via `allow_create`) push-to-create on a miss.
*
* ID-PINNING RATIONALE (design D6, "resolved"): "on first contact the helper
* resolves slug/name → repo_id and pins the immutable id in local git state.
* Names are sugar for humans and first-time clones; every existing checkout
* follows the id and survives any rename." Concretely: an id-form address
* (`org_id`/`prj_...`) already survives a rename (project ids never change),
* so pinning buys it nothing and this module never pins one. A SLUG-form
* address re-resolves `org-slug/name → repo_id` over the network on every
* open unless pinned — and a rename of either half would silently break that
* resolution for an EXISTING checkout the moment it takes effect. Pinning
* `repo_id` the first time a slug-form address resolves means every later
* open on this checkout goes straight to `repo_id` (no resolution round-trip,
* and no exposure to a later rename at all) — structurally better than a
* redirect, per the design's own framing.
*/
import { type VaultRemoteAddress } from "../namespaces/repos.js";
import type { VaultTransport } from "./vault-publication.js";
import type { VaultCreationResult } from "./vault-creation-journal.js";
import type { VaultKeystore } from "./vault-keystore.js";
export interface VaultPinnedRepo {
repo_id: string;
/** `null` if the pin predates this field, or was written by hand. */
resolved_from: {
org_slug: string;
repo_name: string;
} | null;
/** `null` on a legacy pin that predates the id-carrying schema (or one written by hand) — resolution then self-upgrades it through one validation read. */
project_id: string | null;
/** `null` on a legacy pin — see `project_id`. */
org_id: string | null;
/** The coordination room key (`r402.room`) — `null` on a pin written before kygit-handoff. Same value as `project_id` for a project's default room. */
room: string | null;
}
/** Read this checkout's pinned `repo_id`, or `null` when nothing is pinned. */
export declare function readPinnedVaultRepo(repoDir: string): Promise<VaultPinnedRepo | null>;
/**
* Pin `repo_id` into this checkout's LOCAL git config — and, when resolved
* from a slug-form address, the address it was resolved from (diagnostic
* only). `resolvedFrom` is omitted for an id-form pin (vault-client-
* round-trips design D4): an id-form address has no org-slug/name pair to
* record, and — per {@link resolveVaultAddress}'s own doc comment — the
* design's instruction is that pinning "only resolves names"; id-form is
* already rename-proof, so this pin exists purely to skip the resolution
* ROUND TRIP, not to survive a rename.
*/
export declare function pinVaultRepo(repoDir: string, repoId: string, resolvedFrom?: {
org_slug: string;
repo_name: string;
}, ids?: {
project_id: string;
org_id: string;
room_key?: string;
}): Promise<void>;
/**
* add-room-invite design D10: a bare `rooms join <key>` arrival standing
* inside a git repository pins `r402.orgId`/`r402.room` in this checkout's
* LOCAL git config — the SAME two keys {@link pinVaultRepo} writes
* alongside a vault's `r402.repoId` — but WITHOUT one, since a room-only
* join has no vault to pin. Read back by {@link readPinnedRoomBinding},
* independent of {@link readPinnedVaultRepo}'s own gate on `r402.repoId`
* being present (a room-only checkout never sets it, so that reader would
* otherwise see this pin as absent).
*/
export declare function pinRoomBinding(repoDir: string, ids: {
org_id: string;
room_key: string;
}): Promise<void>;
/** Read `r402.orgId`/`r402.room` directly, with NO gate on `r402.repoId` (design D10) — the counterpart read for {@link pinRoomBinding}. Either field is `null` when unset (no repository, or never pinned). */
export declare function readPinnedRoomBinding(repoDir: string): Promise<{
org_id: string | null;
room: string | null;
}>;
export interface VaultAddressResolution {
repo_id: string;
project_id: string;
org_id: string;
form: "id" | "slug";
/**
* How this resolution happened: a local git-config pin, the local
* keystore's own project→repo file (vault-offline-clone-resolve,
* id-form only — {@link VaultKeystore.findRepoByProject}), a live
* network resolution, or a fresh push-to-create. `"pin"` and `"keystore"`
* are BOTH zero-transport-operation answers (see {@link offline}); they
* are kept distinct only so a caller that cares about provenance can tell
* them apart — {@link recoverStaleVaultPin}'s generic `offline` check
* treats them identically.
*/
via: "pin" | "keystore" | "resolved" | "created";
/** The `org-slug/name` the address named, when slug-form; `null` for id-form. */
address: {
org_slug: string;
repo_name: string;
} | null;
/** Set only when THIS call push-to-created the vault (`via === "created"`); `null` otherwise. */
created: {
deduplicated: boolean;
recovery_receipt: VaultCreationResult["recovery_receipt"];
genesis_sha256: string;
} | null;
/**
* `true` iff this resolution performed ZERO transport operations (an
* id-carrying pin). Stale-pin recovery is then the caller's to arm on the
* verb's first repo-scoped read — see {@link recoverStaleVaultPin}.
*/
offline: boolean;
}
export interface ResolveVaultAddressOptions {
keystore: VaultKeystore;
transport: VaultTransport;
/** The parsed remote address (`parseVaultRemoteUrl`'s output) — id-form or slug-form, undiscriminated. */
address: VaultRemoteAddress;
/** The local working tree, for reading/writing the pin. Omit for a repository-free read (e.g. `git ls-remote`) — pinning is then skipped, never a failure. */
repo_dir?: string;
/** Push-to-create on a slug-form miss (D6). `false` (the default) makes a miss an ordinary not-found refusal — the read-only path (`list`/`fetch`, `vault status`). */
allow_create?: boolean;
client_creation_id?: string;
service_public_key?: Uint8Array | string;
/** Fires once, only when THIS call allocated the vault (mirrors `Repos.capture`'s `onVaultCreated`). */
onVaultCreated?: (created: NonNullable<VaultAddressResolution["created"]>) => void | Promise<void>;
/**
* vault-offline-clone-resolve (design D3): recovery-only. Skips BOTH
* local answers — the git-config pin AND the id-form keystore consult —
* forcing a genuine network resolution. Set exclusively by {@link
* recoverStaleVaultPin}'s internal re-resolve call: a stale local
* answer (a pin or a keystore file naming a repo that no longer resolves
* or is no longer ours) must never be allowed to answer the SAME stale
* fact back to itself, which is what would happen if recovery re-ran the
* ordinary local-first path. Never set by an ordinary caller.
*/
bypass_local_answers?: boolean;
}
/**
* Resolve a parsed remote address to `{repo_id, project_id, org_id}`,
* pinning `repo_id` in local git state on the first successful resolution —
* of EITHER form (vault-client-round-trips design D4 widens the
* pre-existing slug-form pin, task 4.5, to id-form too) — and, when
* `allow_create` is set and resolution misses, push-to-create it (task
* 4.4/4.5, design D6). A pinned id that no longer resolves (404) clears the
* pin and re-resolves ONCE through the ordinary form-dispatch path below,
* rather than failing on a stale local pointer.
*/
export declare function resolveVaultAddress(options: ResolveVaultAddressOptions): Promise<VaultAddressResolution>;
/**
* Stale-pin recovery for OFFLINE resolutions (client-surface spec,
* "Id-form remotes pin repo_id like slug-form remotes"; widened by
* vault-offline-clone-resolve design D3 to the keystore-sourced offline
* answer too): a pin or a keystore hit resolves with no network read, so the
* once-in-a-lifetime stale case (a vault deleted and re-allocated out from
* under this checkout, or a keystore file left behind by an old allocation)
* surfaces as the VERB's first repo-scoped read failing instead of a
* resolution failure.
*
* Given that failure, answer the fresh resolution to retry the verb against —
* or `null` when there is nothing to recover:
*
* - a non-vault-absent error (anything outside the not-found family and the
* gateway's authorize-before-reveal `VAULT_ACCESS_DENIED` fold, which
* deliberately makes "gone" and "not yours" one envelope) → `null`,
* rethrow yours;
* - re-resolution yielding the SAME `repo_id` → the local answer was fine
* and the refusal is real: the pin is restored (re-resolution re-pins
* it) and the answer is `null` — recovery never widens what an
* unauthorized caller learns (both probes return the same refusal
* family it already saw);
* - a DIFFERENT `repo_id` → the fresh resolution, pin already rewritten;
* the caller retries the verb exactly once.
*
* The re-resolve MUST bypass every local answer (`bypass_local_answers:
* true`) — a stale keystore file (or, before this change, a stale pin) is
* exactly the fact recovery exists to get past, so re-running the ordinary
* local-first path would just consult the SAME stale local answer again and
* never reach the network. This is why the retry always costs one real
* transport operation, deliberately outside the counted budgets (D3, D5).
*
* Re-resolution failures are swallowed in favor of `null` (the caller's
* original error is the truthful one to surface).
*/
export declare function recoverStaleVaultPin(options: {
keystore: VaultKeystore;
transport: VaultTransport;
address: VaultRemoteAddress;
repo_dir: string;
/** The offline resolution the failing verb ran under. */
resolution: VaultAddressResolution;
/** The verb's failure. */
error: unknown;
}): Promise<VaultAddressResolution | null>;
//# sourceMappingURL=vault-address.d.ts.map