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.
216 lines • 11 kB
TypeScript
/**
* The exact, closed set of refusal codes this module can ever return —
* mirrors the gateway's `WriterStateRefusalCode` exactly (same union, same
* names) so a caller's error mapping needs no translation table.
*/
export type WriterStateRefusalCode = "VALIDATION_FAILED" | "VAULT_WRITER_NOT_ADMITTED" | "HANDOFF_KEY_REVOKED" | "RECIPIENT_SET_MISMATCH" | "EPOCH_ROTATION_WOULD_LEAVE_VAULT_UNCOVERED";
export type WriterStateVerdict = {
ok: true;
} | {
ok: false;
code: WriterStateRefusalCode;
detail: string;
};
export declare const ORG_ROLE_RANK: Readonly<Record<string, number>>;
export declare const WRITER_ELIGIBLE_ROLE_RANK: number;
/** Does `role` meet the writer-eligibility threshold (developer or above)? An unknown/absent role never does. */
export declare function meetsWriterEligibleRole(role: string | null | undefined): boolean;
/**
* vault-multi-writer rev 47 (task 5.5) — predicts the role a handoff mint
* will actually confer, mirroring the gateway's OWN attenuation exactly
* (`services/vault/claims.ts mintHandoff`: `requestedRole &&
* roleRank(requestedRole) < roleRank(minterRole) ? requestedRole :
* minterRole`). The mint's `writer_admission_grant.minted_role` must equal
* this prediction byte-for-byte or the gateway refuses VALIDATION_FAILED —
* after the checkpoint push has already been paid for — so getting the
* comparison direction right matters. An unrecognized `requestedRole`
* (never a legal `OrgRole` server-side either) is treated as "no request":
* a garbage role string fails the gateway's OWN mint validation before its
* attenuation logic — and therefore before this grant-mismatch check —
* ever runs, so predicting `minterRole` for that case is never observed as
* wrong, only moot.
*/
export declare function predictMintedRole(requestedRole: string | undefined, minterRole: string): string;
export interface WriterKeyEntry {
writer_key_id: string;
signing_pubkey: string;
}
/** The full chain-derived writer state at some generation — protocol §4.15. */
export interface WriterChainState {
version: string;
writers: readonly WriterKeyEntry[];
sha256: string;
/** Permanently burned writer_key_ids — protocol §4.15, never re-addable, on this vault, ever. */
burnedWriterKeyIds: ReadonlySet<string>;
/** Permanently consumed handoff_ids — protocol §4.17, a writer_admission_grant is single-use. */
consumedHandoffIds: ReadonlySet<string>;
/**
* Writers a `writer_set_update` REMOVED, with their keys — never part of
* the hashed writer set (`writers`/`sha256` are the chain state), only
* local bookkeeping so a head those writers signed WHILE active can still
* be re-read and verified later (a decrypt catch-up, a restore, a
* checkpoint walk). Admission of a NEW head consults `writers` alone.
*/
retiredWriters?: readonly WriterKeyEntry[];
}
/**
* `vk_` fingerprint of a base64url-encoded Ed25519 public key, or `null` for
* anything that doesn't decode as a canonical 32-byte base64url scalar —
* mirrors the gateway's `writerKeyIdOf` (never throws on a malformed key).
*/
export declare function writerKeyIdOf(signingPubkeyB64u: string): string | null;
/** protocol §4.15: writer_set_sha256 = SHA-256(JCS({format, repo_id, version, writers})). */
export declare function writerSetSha256(repoId: string, version: string, writers: readonly WriterKeyEntry[]): string;
/**
* protocol §4.15: the version-0 singleton, derived from `creator_signing_pubkey`
* (see the module doc for why this diverges from reading a `writer_key_id`
* field directly).
*/
export declare function initialWriterState(repoId: string, genesis: {
creator_signing_pubkey: string;
}): WriterChainState;
/**
* protocol §4.16: apply an ALREADY-ADMITTED `add_writer_key` transition.
* Caller MUST have validated it first (`validateAddWriterKeyPayload`) — this
* function performs no checks, it only advances state.
*/
/**
* vault-multi-writer (task 5.6) — builds a well-formed `handoff`-door
* `add_writer_key` payload (schema `r402s.add-writer-key/v1`) from a
* predecessor writer-set pin and the claimant's own key — the pure
* assembly step `Vault.submitWriterActivationHead` delegates to,
* kept separate so it can be unit-tested directly against
* {@link validateAddWriterKeyPayload} without a full vault/keystore/git
* harness. `predecessorPin` needs only `{version, sha256, writers}` — the
* caller's persisted `writer_set_pin` shape already matches, no
* `WriterChainState` (with its Sets) is required to build an OUTGOING
* payload; only to VALIDATE an incoming one.
*/
export declare function buildAddWriterKeyActivationPayload(repoId: string, predecessorPin: {
version: string;
sha256: string;
writers: readonly WriterKeyEntry[];
}, addedWriter: WriterKeyEntry & {
principal_id: string;
}, handoffId: string, grant: Record<string, unknown>, acceptance: Record<string, unknown>): AddWriterKeyPayload;
/**
* vault-multi-writer (task 5.7) — builds a well-formed `writer`-door
* `add_writer_key` payload from a predecessor writer-set pin and the
* candidate's own published signing key. Unlike {@link
* buildAddWriterKeyActivationPayload} (the handoff door), this authorization
* carries no grant/acceptance — `validateAddWriterKeyPayload`'s `"writer"`
* branch licenses it purely by the CARRYING HEAD's own signer already being
* an active writer (checked at admission time, both client-side replay and
* gateway-side `checkTransitionAdmissible`), so the acting writer's identity
* never appears IN the payload itself, only in the head that carries it.
*/
export declare function buildWriterDoorAddWriterKeyPayload(repoId: string, predecessorPin: {
version: string;
sha256: string;
writers: readonly WriterKeyEntry[];
}, addedWriter: WriterKeyEntry & {
principal_id: string;
}): AddWriterKeyPayload;
export declare function applyAddWriterKey(repoId: string, state: WriterChainState, addedWriter: WriterKeyEntry, consumedHandoffId: string | null): WriterChainState;
/**
* protocol §4.18: apply an ALREADY-ADMITTED `rotate_epoch{writer_set_update}`.
* Caller MUST have validated it first (`validateWriterSetUpdate`). `writers`
* MAY be empty — the explicit sole-writer `writer_key_revoked` read-only
* terminal (D228) is a legal resulting state, not an error at this layer.
*/
export declare function applyWriterSetUpdate(repoId: string, state: WriterChainState, removedWriterKeyIds: readonly string[]): WriterChainState;
/**
* The FIRST, ordinary-case lookup (protocol §5A step 1): is this head's own
* `writer_key_id` already active in the predecessor writer state? A null
* result does not by itself mean refusal — the caller still checks for a
* qualifying `add_writer_key{"handoff"}` transition naming exactly this key
* before concluding `VAULT_WRITER_NOT_ADMITTED`.
*/
export declare function resolveActiveWriter(state: WriterChainState, headWriterKeyId: string): WriterKeyEntry | null;
/**
* The HISTORICAL lookup: a writer that is active OR was removed by a later
* `writer_set_update` (`retiredWriters`). For re-reading a head the chain
* ALREADY admitted — never for admitting a new one, which is
* {@link resolveActiveWriter}'s job alone.
*/
export declare function resolveKnownWriter(state: WriterChainState, headWriterKeyId: string): WriterKeyEntry | null;
export interface AddWriterKeyPayload {
schema: "r402s.add-writer-key/v1";
repo_id: string;
base_writer_set: {
version: string;
sha256: string;
};
next_writer_set: {
version: string;
writers: WriterKeyEntry[];
sha256: string;
};
added_writer: {
writer_key_id: string;
signing_pubkey: string;
principal_id: string;
};
authorization: {
kind: "writer";
} | {
kind: "handoff";
grant: Record<string, unknown>;
acceptance: Record<string, unknown>;
};
}
/**
* The FULL cryptographic + structural validation of an `add_writer_key`
* payload (protocol §4.16/§4.17), given the writer state at the PREDECESSOR
* generation and the carrying head's own `writer_key_id`. Does NOT check
* `authorization.kind:"writer"`'s org-membership eligibility (active
* membership >= developer, directory possession-verification) — that is
* gateway-only, live-state-dependent, and re-checked under the admission
* fence server-side; a client validating its OWN candidate transition before
* submission relies on the gateway's fence-time re-check as the backstop.
*/
export declare function validateAddWriterKeyPayload(repoId: string, predecessorState: WriterChainState, payload: AddWriterKeyPayload, headWriterKeyId: string): WriterStateVerdict;
export interface WriterSetUpdatePayload {
base_version: string;
base_sha256: string;
next_version: string;
next_sha256: string;
removed: readonly {
writer_key_id: string;
principal_id: string;
reason: string;
}[];
writers: readonly WriterKeyEntry[];
}
/**
* The FULL cryptographic + structural validation of a `rotate_epoch`
* transition's `writer_set_update` field (protocol §4.18, D227), given the
* writer state at the PREDECESSOR generation, the carrying head's own
* `writer_key_id`, and the caller's own belief about which writer keys are
* due for removal (`gatewayBlockedWriterKeyIds` — for a client building a
* CANDIDATE rotation this is `state read (blocked set)`; the gateway's own
* fence-time re-check is the source of truth at admission). `allowEmptyResult`
* is true ONLY for the explicit, owner+step-up, sole remaining writer
* `writer_key_revoked` removal (D228); every other call site passes `false`.
*/
export declare function validateWriterSetUpdate(repoId: string, predecessorState: WriterChainState, update: WriterSetUpdatePayload, headWriterKeyId: string, gatewayBlockedWriterKeyIds: ReadonlySet<string>, allowEmptyResult: boolean): WriterStateVerdict;
/**
* DR / offline replay (protocol §5A "Writer-set DR"): rebuild the writer
* state purely from genesis + admitted writer-changing transitions, in
* order. Every transition MUST already be admitted (this performs no
* validation — apply-only, mirroring `applyAddWriterKey`/`applyWriterSetUpdate`).
*/
export type AdmittedWriterTransition = {
kind: "add_writer_key";
addedWriter: WriterKeyEntry;
consumedHandoffId: string | null;
} | {
kind: "writer_set_update";
removedWriterKeyIds: readonly string[];
};
export declare function replayWriterState(repoId: string, genesis: {
creator_signing_pubkey: string;
}, transitions: readonly AdmittedWriterTransition[]): WriterChainState;
/** protocol §4.15: `MAX_VAULT_WRITERS`. A writer-changing transition producing more active writers than this is refused. */
export declare const MAX_VAULT_WRITERS = 64;
//# sourceMappingURL=vault-writer-state.d.ts.map