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.
244 lines • 15.2 kB
TypeScript
import { VAULT_FORMAT, VAULT_SUITE } from "../namespaces/vault.crypto.js";
import { type RedeemKind, type HandoffKeyPrefixEntry, type RedeemKeyParts, type HandoffKeyParts, type InviteKeyParts, type HandoffSecrets, HANDOFF_KEY_PREFIXES, uuidToBytes, assembleHandoffKey, assembleInviteKey, parseRedeemKey, parseHandoffKey, parseInviteKey, deriveHandoffSecrets, deriveInviteSecrets } from "./bearer-redeem-key.js";
export { HANDOFF_KEY_PREFIXES, uuidToBytes, assembleHandoffKey, assembleInviteKey, parseRedeemKey, parseHandoffKey, parseInviteKey, deriveHandoffSecrets, deriveInviteSecrets, };
export type { RedeemKind, HandoffKeyPrefixEntry, RedeemKeyParts, HandoffKeyParts, InviteKeyParts, HandoffSecrets };
/**
* Third HKDF output of a redeem key's `master_secret` (design D4, protocol
* §4.17; kind-parameterized by kygit-invite design D3): the one-use
* admission Ed25519 seed. Derived directly from `master_secret` —
* deliberately NEVER from `auth_secret` or `wrap_key` — so the gateway,
* which receives `auth_secret` at redeem, stays computationally unable to
* derive this seed and manufacture a different redeemer completion. The info
* string is domain-separated by kind (`kygit/<kind>/writer-admission/v1`),
* so an invite's admission seed never matches a handoff's for the same id
* and master secret.
*/
export declare function deriveRedeemWriterAdmissionSeed(kind: RedeemKind, idBytes: Uint8Array, masterSecret: Uint8Array): Uint8Array;
/** Handoff-bound alias of {@link deriveRedeemWriterAdmissionSeed} — `kygit/handoff/writer-admission/v1`. */
export declare function deriveWriterAdmissionSeed(handoffIdBytes: Uint8Array, masterSecret: Uint8Array): Uint8Array;
/** The invite-kind sibling of {@link deriveWriterAdmissionSeed} — `kygit/invite/writer-admission/v1` (kygit-invite design D3). */
export declare function deriveInviteWriterAdmissionSeed(inviteIdBytes: Uint8Array, masterSecret: Uint8Array): Uint8Array;
export type VaultWriterMintedRole = "owner" | "admin" | "developer" | "billing" | "viewer";
/** Signed by the MINTING writer's own writer key, at handoff mint time. Never chain-stored directly — embedded verbatim inside an admitted `add_writer_key` transition's `authorization.kind:"handoff".grant` once consumed. */
export interface WriterAdmissionGrant {
format: typeof VAULT_FORMAT;
object_kind: "writer_admission_grant";
suite: typeof VAULT_SUITE;
repo_id: string;
handoff_id: string;
auth_hash: string;
checkpoint_generation: string;
checkpoint_head_sha256: string;
grantor_writer_key_id: string;
handoff_admission_pubkey: string;
minted_role: VaultWriterMintedRole;
claim_not_after: string;
created_at: string;
signature: string;
}
export interface BuildWriterAdmissionGrantInput {
repo_id: string;
handoff_id: string;
/** The existing kygit-handoff `auth_hash` (from {@link deriveHandoffSecrets}), unrelated to this grant's own signature. */
auth_hash: string;
checkpoint_generation: string;
checkpoint_head_sha256: string;
minted_role: VaultWriterMintedRole;
claim_not_after: string;
created_at?: string;
/** The MINTER's own existing active writer signing seed — NOT {@link deriveWriterAdmissionSeed}'s one-use seed, which belongs to the grant's `handoff_admission_pubkey` field, never to its signer. */
grantor_signing_seed: Uint8Array;
/** Raw 32-byte Ed25519 public key: `ed25519PublicKey(deriveWriterAdmissionSeed(...))`. */
handoff_admission_pubkey: Uint8Array;
}
/** Mint side (D4): builds + signs `writer_admission_grant`. The gateway's own mint-time validation (grantor active + self-consistent, field equality) is server-side; this function only builds a well-formed, correctly-signed object. */
export declare function buildWriterAdmissionGrant(input: BuildWriterAdmissionGrantInput): WriterAdmissionGrant;
/**
* Structural + signature verification of a `writer_admission_grant` against
* the grantor's writer signing pubkey (raw bytes) — does NOT check chain
* state (grantor active in the predecessor writer state, `handoff_id`
* unconsumed). That is the caller's job: gateway-side at admission, or a
* client-side pre-check before spending a grant it did not itself mint.
*/
export declare function verifyWriterAdmissionGrant(grant: WriterAdmissionGrant, grantorSigningPubkeyRaw: Uint8Array): boolean;
/** The statement `writer_acceptance` signs TWICE (design D225's fold of the consultation's separate `writer_key_proof` into a second signature over this same statement). */
export declare const HANDOFF_WRITER_ACCEPT_DOMAIN: "r402s/v0/handoff-writer-accept/v1";
export interface HandoffWriterAcceptStatement {
domain: typeof HANDOFF_WRITER_ACCEPT_DOMAIN;
handoff_id: string;
auth_hash: string;
writer_key_id: string;
signing_pubkey: string;
encryption_pubkey: string;
encryption_fingerprint: string;
}
/** ONE object, TWO signatures over the SAME statement: possession of the one-use admission capability (`acceptance_signature`) AND possession of the redeemer's own permanent writer key (`possession_signature`). Never chain-stored directly — embedded verbatim inside an admitted `add_writer_key` transition's `authorization.kind:"handoff".acceptance` once consumed. */
export interface WriterAcceptance {
statement: HandoffWriterAcceptStatement;
acceptance_signature: string;
possession_signature: string;
}
export interface BuildWriterAcceptanceInput {
handoff_id: string;
auth_hash: string;
/** {@link deriveWriterAdmissionSeed}'s output — B derives it the SAME way A did, from its own copy of `master_secret`. */
admission_seed: Uint8Array;
/** The redeemer's OWN permanent writer signing seed — becomes `writer_key_id`/`signing_pubkey` on the statement. */
redeemer_signing_seed: Uint8Array;
redeemer_encryption_pubkey_raw: Uint8Array;
}
/**
* Redeem side (D4): the redeemer can construct BOTH signatures before ever
* seeing the stored grant, since it already knows `handoff_id`, `auth_hash`
* (recomputed exactly as the existing redeem flow already does via
* {@link deriveHandoffSecrets}), and its own keys.
*/
export declare function buildWriterAcceptance(input: BuildWriterAcceptanceInput): WriterAcceptance;
/**
* Verifies BOTH signatures over the SAME statement: `acceptance_signature`
* under the grant's `handoff_admission_pubkey` (proves B legitimately
* redeemed THIS handoff), `possession_signature` under the statement's own
* `signing_pubkey` (proves `writer_key_id`/`signing_pubkey` really belong
* to whoever is submitting it, not a replayed statement about someone
* else's key). Does not check `handoff_id`/`auth_hash` binding to a stored
* grant, or single-use — those are the caller's job.
*/
export declare function verifyWriterAcceptance(acceptance: WriterAcceptance, admissionPubkeyRaw: Uint8Array): boolean;
/** The literal payload the mint side seals and the redeem side opens, for the handoff kind. */
export interface HandoffEnvelopePayload {
v: 1;
kind: "handoff";
repo_id: string;
/** The pinned epoch (16-hex), matching the vault's `epoch` at capture time. */
epoch: string;
/** `K_e` for `epoch`, lowercase hex — the vault's own symmetric key, delivered directly. */
k_e_hex: string;
/**
* Every epoch key the minter holds (`epoch` → lowercase hex), so a handoff
* minted after an epoch rotation lets the recipient open the pre-rotation
* generations too. Optional: absent on an envelope minted before this
* field existed, in which case `k_e_hex` for `epoch` is all the recipient
* gets (a vault that never rotated needs nothing more).
*/
epoch_keys?: Record<string, string>;
checkpoint: {
generation: string;
commit_oid: string;
};
note_schema: "kygit.handoff-note.v1";
}
/** Frame tag for the handoff envelope format — DISTINCT from `r402s/v0`'s `"R402S0"` object frame (design D3: never stored as a vault object). The LEGACY v1 tag: nothing seals it any more, but a pre-vault-multi-writer handoff row still carries one and {@link openHandoffEnvelope} still opens it. */
export declare const HANDOFF_ENVELOPE_KIND: "kygit-handoff-envelope-v1";
/** Seal the handoff payload under `wrap_key` as a LEGACY v1 envelope. Returns the standard-base64 wire form (openapi `format: byte`) + its declared kind tag. Nothing in the shipped flow calls this any more — {@link sealHandoffEnvelopeV2} is what `handoff` seals — but the v1 shape stays openable and sealable for the conformance vectors. */
export declare function sealHandoffEnvelope(handoffIdBytes: Uint8Array, wrapKey: Uint8Array, payload: HandoffEnvelopePayload, nonce?: Uint8Array): {
sealed_envelope: string;
envelope_kind: string;
};
/** Open a sealed handoff envelope under `wrap_key` (standard base64 as the redeem response carries it, or base64url). Throws `HANDOFF_ENVELOPE_INVALID` on any header mismatch, a `v:2` (or otherwise non-v1) payload shape, and `HANDOFF_AEAD_AUTH_FAILURE` on a bad key/AAD/ciphertext. */
export declare function openHandoffEnvelope(handoffIdBytes: Uint8Array, wrapKey: Uint8Array, sealedEnvelope: string, envelopeKind?: string): HandoffEnvelopePayload;
/** The v2 envelope kind the handoff mint seals. */
export declare const HANDOFF_ENVELOPE_V2_KIND: "kygit-handoff-envelope-v2";
/** The v2 envelope kind the invite mint seals (kygit-invite design D3) — its own tag, never confusable with the handoff one even for the same id. */
export declare const INVITE_ENVELOPE_V2_KIND: "kygit-invite-envelope-v2";
/**
* `v: 2` (NOT 1) is the payload's own shape discriminator, independent of
* `envelope_kind` — a v1 opener's `p.v !== 1` shape check therefore refuses
* a v2 payload outright (`HANDOFF_ENVELOPE_INVALID`) rather than silently
* misreading it as v1; `v` and `envelope_kind` agreeing is exactly what
* makes either one a meaningful, non-spoofable version signal.
*/
export interface HandoffEnvelopePayloadV2 extends Omit<HandoffEnvelopePayload, "v"> {
v: 2;
writer_admission_grant_sha256: string;
}
/** The invite-kind sibling of {@link HandoffEnvelopePayloadV2} — identical shape but for its `kind`/`note_schema` tags (kygit-invite design D3: "the payload is otherwise identical to the handoff envelope — the room does NOT ride in the envelope"). */
export interface InviteEnvelopePayloadV2 extends Omit<HandoffEnvelopePayloadV2, "kind" | "note_schema"> {
kind: "invite";
note_schema: "kygit.invite-note.v1";
}
/** Seal a v2 handoff envelope — identical framing to {@link sealHandoffEnvelope}, tagged `kygit-handoff-envelope-v2` so its AAD (and therefore its ciphertext) is never confusable with a v1 envelope of the same handoff. */
export declare function sealHandoffEnvelopeV2(handoffIdBytes: Uint8Array, wrapKey: Uint8Array, payload: HandoffEnvelopePayloadV2, nonce?: Uint8Array): {
sealed_envelope: string;
envelope_kind: string;
};
/** The invite-kind sibling of {@link sealHandoffEnvelopeV2} — the ONLY invite envelope there is (kygit-invite design D3). */
export declare function sealInviteEnvelope(inviteIdBytes: Uint8Array, wrapKey: Uint8Array, payload: InviteEnvelopePayloadV2, nonce?: Uint8Array): {
sealed_envelope: string;
envelope_kind: string;
};
/**
* Open a v2 envelope for a writer-activation flow. Refuses a v1 (or any
* other non-v2) `envelope_kind` LOCALLY with `HANDOFF_ENVELOPE_UNSUPPORTED`
* BEFORE attempting to decrypt anything — post-vault-multi-writer, a
* writer-activation-aware `resume` needs `writer_admission_grant_sha256` to
* cross-check the returned grant, and a pre-rev-47 v1 envelope structurally
* has none. Shares ONLY the frame/AEAD-opening step with
* {@link openHandoffEnvelope} ({@link openRedeemEnvelope}) — NOT that
* function itself, since its `v !== 1` shape check would (correctly) refuse
* a v2 payload; this function applies its own `v !== 2` shape check
* instead. Callers that only need K_repo/checkpoint delivery, with no
* writer activation, keep using {@link openHandoffEnvelope} against a v1
* envelope as before — v1 and v2 are NOT interchangeable at the shape
* layer, by design.
*/
export declare function openHandoffEnvelopeV2(handoffIdBytes: Uint8Array, wrapKey: Uint8Array, sealedEnvelope: string, envelopeKind: string): HandoffEnvelopePayloadV2;
/** The invite-kind sibling of {@link openHandoffEnvelopeV2} — throws `INVITE_ENVELOPE_UNSUPPORTED` / `INVITE_ENVELOPE_INVALID` / `INVITE_AEAD_AUTH_FAILURE` (kygit-invite design D3). */
export declare function openInviteEnvelope(inviteIdBytes: Uint8Array, wrapKey: Uint8Array, sealedEnvelope: string, envelopeKind?: string): InviteEnvelopePayloadV2;
export interface KygitHandoffNoteCapture {
base_head: string;
branch: string | null;
modified_captured: number;
untracked_captured: number;
sensitive_excluded: string[];
ignored_not_transferred_count: number;
}
/** The fields shared by every redeem-kind note — free-text fields are Markdown. */
interface KygitRedeemNoteBase {
created_at: string;
from: {
agent: string;
harness?: string;
model?: string;
};
summary: string;
completed?: string[];
in_progress?: string[];
failing?: string[];
tried?: string[];
next_steps?: string[];
commands?: {
test?: string;
build?: string;
run?: string;
};
decisions?: string[];
open_questions?: string[];
capture: KygitHandoffNoteCapture;
}
/** `kygit.handoff-note.v1` — the handoff commit's message, verbatim JSON. */
export interface KygitHandoffNote extends KygitRedeemNoteBase {
schema: "kygit.handoff-note.v1";
}
/** `kygit.invite-note.v1` — the invite commit's message, verbatim JSON. Same fields as {@link KygitHandoffNote} (kygit-invite design D3). */
export interface KygitInviteNote extends KygitRedeemNoteBase {
schema: "kygit.invite-note.v1";
}
export declare function isKygitHandoffNote(value: unknown): value is KygitHandoffNote;
export declare function isKygitInviteNote(value: unknown): value is KygitInviteNote;
export interface HandoffNoteSecretFinding {
field: string;
reason: string;
}
/**
* Refuse a note that carries a bare secret — no override flag exists
* (design D10): a note is read by another agent, and secrets have the
* secrets API. `null` when the note is clean.
*/
export declare function scanHandoffNoteForSecrets(note: KygitHandoffNote): HandoffNoteSecretFinding | null;
/** The invite-kind sibling of {@link scanHandoffNoteForSecrets} (kygit-invite design D3). */
export declare function scanInviteNoteForSecrets(note: KygitInviteNote): HandoffNoteSecretFinding | null;
/** Throws `HANDOFF_NOTE_CONTAINS_SECRET` naming the field — call before the handoff commit is written. */
export declare function assertHandoffNoteHasNoSecret(note: KygitHandoffNote): void;
/** Throws `INVITE_NOTE_CONTAINS_SECRET` naming the field — call before the invite commit is written (kygit-invite design D3). */
export declare function assertInviteNoteHasNoSecret(note: KygitInviteNote): void;
//# sourceMappingURL=vault-handoff.d.ts.map