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.
303 lines • 18.7 kB
JavaScript
/**
* 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 { hardenedGit } from "./vault-snapshot.js";
import { vaultRemoteAddressForm } from "../namespaces/repos.js";
import { pushToCreateVault } from "./vault-push-to-create.js";
const CONFIG_KEY_REPO_ID = "r402.repoId";
/** The `org-slug/name` text the pin was resolved from — diagnostic only; resolution never reads it back. */
const CONFIG_KEY_ADDRESS = "r402.repoAddress";
/**
* The resolved ids beside the pin (vault-force-spelling-and-pin-fold).
* A pin carrying BOTH resolves fully OFFLINE — zero transport operations —
* which is what removes the standing per-invocation validation read; a
* legacy pin without them self-upgrades through one validation read.
*/
const CONFIG_KEY_PROJECT_ID = "r402.projectId";
const CONFIG_KEY_ORG_ID = "r402.orgId";
/**
* kygit-handoff design D10: the coordination-room key, written ALONGSIDE
* `r402.projectId` (same value — a project's DEFAULT room is keyed by its
* project id, p0016) so `cli/lib/org-context.mjs`'s room resolution has a
* dedicated config key to read rather than special-casing vault's own
* `r402.projectId` name. Every existing pin site gets this for free, not
* just `resume` — one write, every vault checkout benefits.
*/
const CONFIG_KEY_ROOM = "r402.room";
async function readLocalGitConfig(repoDir, key) {
try {
const out = (await hardenedGit(repoDir, ["config", "--local", "--get", key])).text().trim();
return out.length > 0 ? out : null;
}
catch {
// `git config --get` exits non-zero both when the key is absent and when
// this is not a repository at all — either way, "nothing pinned" is the
// correct read for a resolver that must degrade gracefully.
return null;
}
}
/** Read this checkout's pinned `repo_id`, or `null` when nothing is pinned. */
export async function readPinnedVaultRepo(repoDir) {
const repoId = await readLocalGitConfig(repoDir, CONFIG_KEY_REPO_ID);
if (!repoId)
return null;
const address = await readLocalGitConfig(repoDir, CONFIG_KEY_ADDRESS);
const parts = address ? address.split("/") : [];
const resolvedFrom = parts.length === 2 && parts[0] && parts[1] ? { org_slug: parts[0], repo_name: parts.slice(1).join("/") } : null;
const projectId = await readLocalGitConfig(repoDir, CONFIG_KEY_PROJECT_ID);
const orgId = await readLocalGitConfig(repoDir, CONFIG_KEY_ORG_ID);
const room = await readLocalGitConfig(repoDir, CONFIG_KEY_ROOM);
return { repo_id: repoId, resolved_from: resolvedFrom, project_id: projectId, org_id: orgId, room };
}
/**
* 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 async function pinVaultRepo(repoDir, repoId, resolvedFrom, ids) {
await hardenedGit(repoDir, ["config", "--local", CONFIG_KEY_REPO_ID, repoId]);
if (resolvedFrom)
await hardenedGit(repoDir, ["config", "--local", CONFIG_KEY_ADDRESS, `${resolvedFrom.org_slug}/${resolvedFrom.repo_name}`]);
if (ids) {
await hardenedGit(repoDir, ["config", "--local", CONFIG_KEY_PROJECT_ID, ids.project_id]);
await hardenedGit(repoDir, ["config", "--local", CONFIG_KEY_ORG_ID, ids.org_id]);
// kygit-invite design D3/D5: the room pin defaults to the project id (a
// project's default room) but a joiner pins the INVITE's own room key
// when the mint named one — `ids.room_key` overrides, never silently
// widens what an ordinary resume/push pin writes.
await hardenedGit(repoDir, ["config", "--local", CONFIG_KEY_ROOM, ids.room_key ?? ids.project_id]);
}
}
/**
* 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 async function pinRoomBinding(repoDir, ids) {
await hardenedGit(repoDir, ["config", "--local", CONFIG_KEY_ORG_ID, ids.org_id]);
await hardenedGit(repoDir, ["config", "--local", CONFIG_KEY_ROOM, ids.room_key]);
}
/** 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 async function readPinnedRoomBinding(repoDir) {
const org_id = await readLocalGitConfig(repoDir, CONFIG_KEY_ORG_ID);
const room = await readLocalGitConfig(repoDir, CONFIG_KEY_ROOM);
return { org_id, room };
}
/** Clear a pin this checkout no longer trusts (design D4: a pinned id that 404s). Tolerates an absent key — `git config --unset` on a key that was never set is not a failure here. */
async function clearPinnedVaultRepo(repoDir) {
for (const key of [CONFIG_KEY_REPO_ID, CONFIG_KEY_ADDRESS, CONFIG_KEY_PROJECT_ID, CONFIG_KEY_ORG_ID, CONFIG_KEY_ROOM]) {
try {
await hardenedGit(repoDir, ["config", "--local", "--unset", key]);
}
catch {
// already absent — nothing to clear
}
}
}
/** `RESOURCE_NOT_FOUND` (or a bare 404) — the "this pin no longer resolves" signal, never any other failure. */
function isPinStale(e) {
const err = e;
return Boolean(err && (err.status === 404 || err.code === "RESOURCE_NOT_FOUND" || err.code === "ROUTE_NOT_FOUND"));
}
/**
* 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 async function resolveVaultAddress(options) {
if (options.repo_dir && !options.bypass_local_answers) {
const pinned = await readPinnedVaultRepo(options.repo_dir);
if (pinned) {
// `form` below describes what THIS call was asked to resolve, not how
// the pin originally came to exist (`resolved_from` alone can't tell —
// it is `null` for both an id-form pin and a pin that predates the
// field/was written by hand).
if (pinned.project_id && pinned.org_id) {
// Id-carrying pin: fully OFFLINE — zero transport operations. The
// stale-pin case (the vault deleted out from under this checkout)
// surfaces on the verb's first repo-scoped read instead; the caller
// arms {@link recoverStaleVaultPin} for that (client-surface
// spec, "Id-form remotes pin repo_id like slug-form remotes").
return { repo_id: pinned.repo_id, project_id: pinned.project_id, org_id: pinned.org_id, form: vaultRemoteAddressForm(options.address), via: "pin", address: pinned.resolved_from, created: null, offline: true };
}
try {
// Legacy id-less pin: one validation read, then REWRITE the pin with
// the ids so every later invocation on this checkout is offline —
// self-upgrading, no migration step.
const record = await options.transport.getVaultRecord({ repo_id: pinned.repo_id });
await pinVaultRepo(options.repo_dir, record.repo_id, pinned.resolved_from ?? undefined, { project_id: record.project_id, org_id: record.org_id });
return { repo_id: record.repo_id, project_id: record.project_id, org_id: record.org_id, form: vaultRemoteAddressForm(options.address), via: "pin", address: pinned.resolved_from, created: null, offline: false };
}
catch (e) {
if (!isPinStale(e))
throw e;
await clearPinnedVaultRepo(options.repo_dir);
// fall through to an ordinary (non-pinned) resolution, below
}
}
}
const form = vaultRemoteAddressForm(options.address);
if (form === "id") {
if (!options.bypass_local_answers) {
// vault-offline-clone-resolve (design D1/D2): consult the LOCAL
// keystore before the network. D2's completeness argument: decrypting
// anything from this vault requires exactly the keystore's repo file
// (`k_repo_hex`), so a machine without one fails the clone at
// `VAULT_REPO_STATE_MISSING` no matter how resolution went —
// "keystore-known" is therefore exactly the set of clones that can
// actually complete, and the network read was only ever confirming a
// fact this machine already held (110-530ms of RTT for 13-22ms of
// server work). An id-form address always
// carries a real `org_id` (see `vaultRemoteAddressForm`), so the
// lookup is always org-scoped — a project-id match under a different
// org is a miss, never a guess. Deliberately runs even with NO
// `repo_dir` (a repo-free `git ls-remote`): the keystore is
// cwd-independent, unlike the git-config pin above.
const keystoreMatch = options.keystore.findRepoByProject(options.address.project_id, options.address.org_id);
if (keystoreMatch) {
// Same offline handle shape the git-config pin branch above builds;
// `via: "keystore"` is the only distinguishing mark, so {@link
// recoverStaleVaultPin}'s generic `resolution.offline` check
// covers this source unchanged. The git-config pin is written
// immediately — "on first successful use" of this answer, exactly
// where the network-resolved branch below writes it — so every
// LATER invocation on this checkout short-circuits through the pin
// check above instead of this keystore scan.
if (options.repo_dir)
await pinVaultRepo(options.repo_dir, keystoreMatch.repo_id, undefined, { project_id: keystoreMatch.project_id, org_id: keystoreMatch.org_id });
return { repo_id: keystoreMatch.repo_id, project_id: keystoreMatch.project_id, org_id: keystoreMatch.org_id, form: "id", via: "keystore", address: null, created: null, offline: true };
}
}
const record = await options.transport.findVaultByProject({ project_id: options.address.project_id });
// Design D4: an id-form address is already rename-proof (a project id
// never changes), so this pin exists purely to skip the RESOLUTION
// round trip on every later invocation — never to survive a rename, per
// the design's own "pin only resolves names" framing for WHY a pin is
// needed at all; id-form gets one anyway because the round trip itself
// is the cost this change removes.
if (options.repo_dir)
await pinVaultRepo(options.repo_dir, record.repo_id, undefined, { project_id: record.project_id, org_id: record.org_id });
return { repo_id: record.repo_id, project_id: record.project_id, org_id: record.org_id, form: "id", via: "resolved", address: null, created: null, offline: false };
}
const orgSlug = options.address.org_id;
const repoName = options.address.project_id;
// `allow_create` delegates straight to `pushToCreateVault`, which
// already opens with its OWN fast-path `findVaultByRepo` read (step 1 of
// its own doc comment) — probing here first too would just double the
// network read on every ordinary (non-creating) push.
if (options.allow_create) {
const result = await pushToCreateVault({
keystore: options.keystore,
transport: options.transport,
org_slug: orgSlug,
repo_name: repoName,
...(options.client_creation_id !== undefined ? { client_creation_id: options.client_creation_id } : {}),
...(options.service_public_key !== undefined ? { service_public_key: options.service_public_key } : {}),
});
if (options.repo_dir)
await pinVaultRepo(options.repo_dir, result.repo_id, { org_slug: orgSlug, repo_name: repoName }, { project_id: result.project_id, org_id: result.org_id });
if (!result.found && result.created)
await options.onVaultCreated?.(result.created);
return { repo_id: result.repo_id, project_id: result.project_id, org_id: result.org_id, form: "slug", via: result.found ? "resolved" : "created", address: { org_slug: orgSlug, repo_name: repoName }, created: result.created, offline: false };
}
// Read-only path (`list`/`fetch`, plain `status`): a miss is an ordinary
// not-found refusal — including `SLUG_RELEASED`, which is NEVER
// auto-followed and rethrows unchanged either way.
const record = await options.transport.findVaultByRepo({ org_slug: orgSlug, repo_name: repoName });
if (options.repo_dir)
await pinVaultRepo(options.repo_dir, record.repo_id, { org_slug: orgSlug, repo_name: repoName }, { project_id: record.project_id, org_id: record.org_id });
return { repo_id: record.repo_id, project_id: record.project_id, org_id: record.org_id, form: "slug", via: "resolved", address: { org_slug: orgSlug, repo_name: repoName }, created: null, offline: false };
}
/**
* 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 async function recoverStaleVaultPin(options) {
if (!options.resolution.offline)
return null;
const err = options.error;
const vaultAbsentSignal = Boolean(err && (isPinStale(err) || err.code === "VAULT_ACCESS_DENIED" || err.status === 403));
if (!vaultAbsentSignal)
return null;
try {
await clearPinnedVaultRepo(options.repo_dir);
const fresh = await resolveVaultAddress({ keystore: options.keystore, transport: options.transport, address: options.address, repo_dir: options.repo_dir, bypass_local_answers: true });
return fresh.repo_id === options.resolution.repo_id ? null : fresh;
}
catch {
return null;
}
}
//# sourceMappingURL=vault-address.js.map