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.
370 lines • 22.6 kB
JavaScript
/**
* r402s/v0 rev 47 — the writer set as CHAIN STATE (protocol §4.15), and the
* pure cryptographic half of `add_writer_key` / `rotate_epoch{writer_set_update}`
* admission (§4.16–§4.18, D221–D227). Client-side mirror of the gateway's
* `services/vault/writer-state.ts` (task vault-multi-writer 2.3) — same
* functions, same Verdict shape, same crypto rules — so both sides reach the
* SAME conclusion from the SAME chain bytes with no shared network call.
*
* Deliberately pure and isomorphic (no I/O, no Node-only APIs): a recovering
* client, the browser viewer, and `r402s-verify` all need to replay writer
* state from nothing but genesis + admitted chain objects. `checkChainLink`'s
* own asserts stay ignorant of transition PAYLOADS (protocol discipline); this
* module is where a transition's writer-set content gets opened and verified,
* once the caller has already established the transition is admitted or is
* validating a candidate before submission.
*
* One deliberate divergence from the gateway's file: `initialWriterState`
* DERIVES the version-0 writer_key_id from `creator_signing_pubkey` rather
* than reading a `genesis.writer_key_id` field — the gateway's own genesis
* admission already asserts `writerKeyIdOf(creator_signing_pubkey) ===
* genesis.writer_key_id` (chain.ts), so for any validly-admitted genesis both
* approaches agree; deriving is simpler for a client that has no reason to
* trust an extra redundant field over the pubkey it already verified.
*/
import { LocalError } from "../errors.js";
import { VAULT_B64U_32_RE, VAULT_B64U_64_RE, ed25519VerifyStrict, fromBase64url, vaultWithoutSignature, isCanonicalBase64url, jcs, sha256Hex, signaturePreimage, vkFingerprint, } from "../namespaces/vault.crypto.js";
// ─── Org role lattice (mirrors org-owned-control-plane's owner > admin > developer > billing > viewer) ──
export const ORG_ROLE_RANK = Object.freeze({
viewer: 0,
billing: 1,
developer: 2,
admin: 3,
owner: 4,
});
export const WRITER_ELIGIBLE_ROLE_RANK = ORG_ROLE_RANK.developer;
/** Does `role` meet the writer-eligibility threshold (developer or above)? An unknown/absent role never does. */
export function meetsWriterEligibleRole(role) {
return !!role && (ORG_ROLE_RANK[role] ?? -1) >= WRITER_ELIGIBLE_ROLE_RANK;
}
/**
* 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 function predictMintedRole(requestedRole, minterRole) {
return requestedRole !== undefined && requestedRole in ORG_ROLE_RANK && ORG_ROLE_RANK[requestedRole] < ORG_ROLE_RANK[minterRole] ? requestedRole : minterRole;
}
const WRITER_SET_FORMAT = "r402s/writer-set/v1";
function asStr(v) {
return typeof v === "string" ? v : null;
}
function sortWriters(writers) {
return [...writers].sort((a, b) => (a.writer_key_id < b.writer_key_id ? -1 : a.writer_key_id > b.writer_key_id ? 1 : 0));
}
function writersArraysEqual(a, b) {
return a.length === b.length && a.every((w, i) => w.writer_key_id === b[i].writer_key_id && w.signing_pubkey === b[i].signing_pubkey);
}
function nextVersionHex16(hex16) {
return (BigInt("0x" + hex16) + 1n).toString(16).padStart(16, "0");
}
/**
* `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 function writerKeyIdOf(signingPubkeyB64u) {
if (!VAULT_B64U_32_RE.test(signingPubkeyB64u) || !isCanonicalBase64url(signingPubkeyB64u))
return null;
return vkFingerprint(fromBase64url(signingPubkeyB64u));
}
/**
* Strict Ed25519 verification of a detached (b64u-encoded) signature over
* `message`, given a b64u-encoded public key. Never throws — a malformed
* scalar is simply `false`, matching `ed25519VerifyStrict`'s own contract.
*/
function verifyDetachedB64u(publicKeyB64u, signatureB64u, message) {
if (!VAULT_B64U_32_RE.test(publicKeyB64u) || !isCanonicalBase64url(publicKeyB64u))
return false;
if (!VAULT_B64U_64_RE.test(signatureB64u) || !isCanonicalBase64url(signatureB64u))
return false;
return ed25519VerifyStrict(fromBase64url(signatureB64u), message, fromBase64url(publicKeyB64u));
}
/** protocol §4.15: writer_set_sha256 = SHA-256(JCS({format, repo_id, version, writers})). */
export function writerSetSha256(repoId, version, writers) {
const writersJson = writers.map((w) => ({ writer_key_id: w.writer_key_id, signing_pubkey: w.signing_pubkey }));
return sha256Hex(jcs({ format: WRITER_SET_FORMAT, repo_id: repoId, version, writers: writersJson }));
}
/**
* 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 function initialWriterState(repoId, genesis) {
const writerKeyId = writerKeyIdOf(genesis.creator_signing_pubkey);
if (writerKeyId === null) {
throw new LocalError("genesis.creator_signing_pubkey is not a canonical base64url Ed25519 public key", "computing the vault's initial writer state", { code: "VAULT_BAD_GENESIS" });
}
const writers = [{ writer_key_id: writerKeyId, signing_pubkey: genesis.creator_signing_pubkey }];
return {
version: "0000000000000000",
writers,
sha256: writerSetSha256(repoId, "0000000000000000", writers),
burnedWriterKeyIds: new Set(),
consumedHandoffIds: new Set(),
};
}
/**
* 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 function buildAddWriterKeyActivationPayload(repoId, predecessorPin, addedWriter, handoffId, grant, acceptance) {
const predecessorState = { version: predecessorPin.version, writers: predecessorPin.writers, sha256: predecessorPin.sha256, burnedWriterKeyIds: new Set(), consumedHandoffIds: new Set() };
const nextState = applyAddWriterKey(repoId, predecessorState, { writer_key_id: addedWriter.writer_key_id, signing_pubkey: addedWriter.signing_pubkey }, handoffId);
return {
schema: "r402s.add-writer-key/v1",
repo_id: repoId,
base_writer_set: { version: predecessorPin.version, sha256: predecessorPin.sha256 },
next_writer_set: { version: nextState.version, writers: [...nextState.writers], sha256: nextState.sha256 },
added_writer: { writer_key_id: addedWriter.writer_key_id, signing_pubkey: addedWriter.signing_pubkey, principal_id: addedWriter.principal_id },
authorization: { kind: "handoff", grant, acceptance },
};
}
/**
* 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 function buildWriterDoorAddWriterKeyPayload(repoId, predecessorPin, addedWriter) {
const predecessorState = { version: predecessorPin.version, writers: predecessorPin.writers, sha256: predecessorPin.sha256, burnedWriterKeyIds: new Set(), consumedHandoffIds: new Set() };
const nextState = applyAddWriterKey(repoId, predecessorState, { writer_key_id: addedWriter.writer_key_id, signing_pubkey: addedWriter.signing_pubkey }, null);
return {
schema: "r402s.add-writer-key/v1",
repo_id: repoId,
base_writer_set: { version: predecessorPin.version, sha256: predecessorPin.sha256 },
next_writer_set: { version: nextState.version, writers: [...nextState.writers], sha256: nextState.sha256 },
added_writer: { writer_key_id: addedWriter.writer_key_id, signing_pubkey: addedWriter.signing_pubkey, principal_id: addedWriter.principal_id },
authorization: { kind: "writer" },
};
}
export function applyAddWriterKey(repoId, state, addedWriter, consumedHandoffId) {
const writers = sortWriters([...state.writers, addedWriter]);
const version = nextVersionHex16(state.version);
return {
version,
writers,
sha256: writerSetSha256(repoId, version, writers),
burnedWriterKeyIds: state.burnedWriterKeyIds,
consumedHandoffIds: consumedHandoffId === null ? state.consumedHandoffIds : new Set([...state.consumedHandoffIds, consumedHandoffId]),
retiredWriters: state.retiredWriters ?? [],
};
}
/**
* 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 function applyWriterSetUpdate(repoId, state, removedWriterKeyIds) {
const removed = new Set(removedWriterKeyIds);
const writers = sortWriters(state.writers.filter((w) => !removed.has(w.writer_key_id)));
const version = nextVersionHex16(state.version);
const retiredNow = state.writers.filter((w) => removed.has(w.writer_key_id));
const retiredWriters = sortWriters([...(state.retiredWriters ?? []).filter((w) => !removed.has(w.writer_key_id)), ...retiredNow]);
return {
version,
writers,
sha256: writerSetSha256(repoId, version, writers),
burnedWriterKeyIds: new Set([...state.burnedWriterKeyIds, ...removed]),
consumedHandoffIds: state.consumedHandoffIds,
retiredWriters,
};
}
/**
* 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 function resolveActiveWriter(state, headWriterKeyId) {
return state.writers.find((w) => w.writer_key_id === headWriterKeyId) ?? 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 function resolveKnownWriter(state, headWriterKeyId) {
return resolveActiveWriter(state, headWriterKeyId) ?? (state.retiredWriters ?? []).find((w) => w.writer_key_id === headWriterKeyId) ?? null;
}
/**
* 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 function validateAddWriterKeyPayload(repoId, predecessorState, payload, headWriterKeyId) {
if (payload.base_writer_set.version !== predecessorState.version || payload.base_writer_set.sha256 !== predecessorState.sha256) {
return { ok: false, code: "VALIDATION_FAILED", detail: "base_writer_set does not match the writer state at the predecessor generation" };
}
const addedKeyId = writerKeyIdOf(payload.added_writer.signing_pubkey);
if (addedKeyId === null || addedKeyId !== payload.added_writer.writer_key_id) {
return { ok: false, code: "VALIDATION_FAILED", detail: "added_writer.writer_key_id does not derive from added_writer.signing_pubkey" };
}
if (predecessorState.burnedWriterKeyIds.has(addedKeyId)) {
return { ok: false, code: "VALIDATION_FAILED", detail: `writer_key_id ${addedKeyId} was permanently removed and can never be re-added` };
}
if (predecessorState.writers.some((w) => w.writer_key_id === addedKeyId)) {
return { ok: false, code: "VALIDATION_FAILED", detail: `writer_key_id ${addedKeyId} is already an active writer` };
}
if (predecessorState.writers.length >= MAX_VAULT_WRITERS) {
return { ok: false, code: "VALIDATION_FAILED", detail: `this vault already has ${MAX_VAULT_WRITERS} active writers, the protocol maximum (MAX_VAULT_WRITERS)` };
}
const expectedNextWriters = sortWriters([
...predecessorState.writers,
{ writer_key_id: addedKeyId, signing_pubkey: payload.added_writer.signing_pubkey },
]);
const expectedNextVersion = nextVersionHex16(predecessorState.version);
const expectedNextSha = writerSetSha256(repoId, expectedNextVersion, expectedNextWriters);
if (payload.next_writer_set.version !== expectedNextVersion ||
payload.next_writer_set.sha256 !== expectedNextSha ||
!writersArraysEqual(payload.next_writer_set.writers, expectedNextWriters)) {
return { ok: false, code: "VALIDATION_FAILED", detail: "next_writer_set is not exactly base_writer_set plus added_writer" };
}
if (payload.authorization.kind === "writer") {
if (headWriterKeyId === addedKeyId) {
return { ok: false, code: "VALIDATION_FAILED", detail: 'authorization.kind:"writer" cannot be self-signed by the added key' };
}
if (!predecessorState.writers.some((w) => w.writer_key_id === headWriterKeyId)) {
return { ok: false, code: "VAULT_WRITER_NOT_ADMITTED", detail: "the carrying head's signer is not an active writer at the predecessor generation" };
}
return { ok: true };
}
// authorization.kind === "handoff"
if (headWriterKeyId !== addedKeyId) {
return { ok: false, code: "VALIDATION_FAILED", detail: "a handoff activation head must be signed by the added key itself" };
}
const { grant, acceptance } = payload.authorization;
const grantorKeyId = asStr(grant.grantor_writer_key_id);
const grantorEntry = grantorKeyId === null ? null : (predecessorState.writers.find((w) => w.writer_key_id === grantorKeyId) ?? null);
if (grantorEntry === null) {
return { ok: false, code: "HANDOFF_KEY_REVOKED", detail: "grantor_not_active" };
}
if (asStr(grant.object_kind) !== "writer_admission_grant") {
return { ok: false, code: "VALIDATION_FAILED", detail: "writer_admission_grant.object_kind is not writer_admission_grant" };
}
const grantSignature = asStr(grant.signature);
const grantPreimage = signaturePreimage("writer_admission_grant", vaultWithoutSignature(grant));
if (grantSignature === null || !verifyDetachedB64u(grantorEntry.signing_pubkey, grantSignature, grantPreimage)) {
return { ok: false, code: "VALIDATION_FAILED", detail: "writer_admission_grant signature does not verify under the grantor's writer key" };
}
const statementRaw = acceptance.statement;
if (typeof statementRaw !== "object" || statementRaw === null || Array.isArray(statementRaw)) {
return { ok: false, code: "VALIDATION_FAILED", detail: "writer_acceptance.statement is missing or malformed" };
}
const statement = statementRaw;
const statementPreimage = signaturePreimage("handoff-writer-accept/v1", statement);
const handoffAdmissionPubkey = asStr(grant.handoff_admission_pubkey);
const acceptanceSignature = asStr(acceptance.acceptance_signature);
if (handoffAdmissionPubkey === null ||
acceptanceSignature === null ||
!verifyDetachedB64u(handoffAdmissionPubkey, acceptanceSignature, statementPreimage)) {
return { ok: false, code: "VALIDATION_FAILED", detail: "writer_acceptance.acceptance_signature does not verify under the grant's admission key" };
}
const possessionSignature = asStr(acceptance.possession_signature);
if (possessionSignature === null ||
!verifyDetachedB64u(payload.added_writer.signing_pubkey, possessionSignature, statementPreimage)) {
return { ok: false, code: "VALIDATION_FAILED", detail: "writer_acceptance.possession_signature does not verify under the added writer's own key" };
}
const handoffId = asStr(grant.handoff_id);
if (handoffId === null || handoffId !== asStr(statement.handoff_id)) {
return { ok: false, code: "VALIDATION_FAILED", detail: "grant.handoff_id != acceptance.statement.handoff_id" };
}
if (asStr(grant.auth_hash) !== asStr(statement.auth_hash)) {
return { ok: false, code: "VALIDATION_FAILED", detail: "grant.auth_hash != acceptance.statement.auth_hash" };
}
if (asStr(statement.writer_key_id) !== addedKeyId || asStr(statement.signing_pubkey) !== payload.added_writer.signing_pubkey) {
return { ok: false, code: "VALIDATION_FAILED", detail: "acceptance.statement writer identity does not match added_writer" };
}
if (predecessorState.consumedHandoffIds.has(handoffId)) {
return { ok: false, code: "VALIDATION_FAILED", detail: `handoff_id ${handoffId} has already been consumed in this vault's writer history` };
}
return { ok: true };
}
/**
* 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 function validateWriterSetUpdate(repoId, predecessorState, update, headWriterKeyId, gatewayBlockedWriterKeyIds, allowEmptyResult) {
if (update.base_version !== predecessorState.version || update.base_sha256 !== predecessorState.sha256) {
return { ok: false, code: "VALIDATION_FAILED", detail: "writer_set_update base does not match the writer state at the predecessor generation" };
}
const removedIds = new Set(update.removed.map((r) => r.writer_key_id));
const blockedSorted = [...gatewayBlockedWriterKeyIds].sort();
const removedSorted = [...removedIds].sort();
const exactMatch = blockedSorted.length === removedSorted.length && blockedSorted.every((id, i) => id === removedSorted[i]);
if (!exactMatch) {
return { ok: false, code: "RECIPIENT_SET_MISMATCH", detail: "writer_set_update.removed does not equal exactly the blocked writer key set" };
}
const expectedWriters = sortWriters(predecessorState.writers.filter((w) => !removedIds.has(w.writer_key_id)));
if (!writersArraysEqual(update.writers, expectedWriters)) {
return { ok: false, code: "RECIPIENT_SET_MISMATCH", detail: "writer_set_update.writers is not base_writer_set minus exactly the removed set" };
}
if (expectedWriters.length === 0 && !allowEmptyResult) {
return { ok: false, code: "EPOCH_ROTATION_WOULD_LEAVE_VAULT_UNCOVERED", detail: "this rotation would leave the vault with zero writers" };
}
if (expectedWriters.length > 0 && !expectedWriters.some((w) => w.writer_key_id === headWriterKeyId)) {
return { ok: false, code: "VALIDATION_FAILED", detail: "the rotation head's own signer must remain in the next writer set" };
}
const expectedNextVersion = nextVersionHex16(predecessorState.version);
const expectedNextSha = writerSetSha256(repoId, expectedNextVersion, expectedWriters);
if (update.next_version !== expectedNextVersion || update.next_sha256 !== expectedNextSha) {
return { ok: false, code: "VALIDATION_FAILED", detail: "writer_set_update next commitment does not match the recomputed removal" };
}
return { ok: true };
}
export function replayWriterState(repoId, genesis, transitions) {
let state = initialWriterState(repoId, genesis);
for (const t of transitions) {
state =
t.kind === "add_writer_key"
? applyAddWriterKey(repoId, state, t.addedWriter, t.consumedHandoffId)
: applyWriterSetUpdate(repoId, state, t.removedWriterKeyIds);
}
return state;
}
/** protocol §4.15: `MAX_VAULT_WRITERS`. A writer-changing transition producing more active writers than this is refused. */
export const MAX_VAULT_WRITERS = 64;
//# sourceMappingURL=vault-writer-state.js.map