UNPKG

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.

69 lines • 2.84 kB
/** * RFC 8785 (JCS) canonical JSON utility — kept as a UX / diagnostic helper. * * **As of v1.34, the gateway is authoritative for the deploy manifest * digest.** `POST /apply/v1/plans` returns the gateway-computed digest in * the response, and operation idempotency keys on it (combined with * `project_id`, `base_release_id`, and an optional client idempotency key). * A drift between this implementation and the gateway's canonicalize * therefore SHALL NOT silently break idempotency — retries still find the * existing plan because the lookup uses the gateway-computed value. * * This module remains exported so callers can still produce a stable local * digest for caching or debugging output. New code should treat the local * digest as advisory. * * Spec for our value domain: * - object keys sorted ASCII-ascending * - arrays comma-separated, no whitespace * - strings/numbers via JSON.stringify (matches RFC 8785 for ASCII paths, * hex sha256, integer sizes, ASCII content_types) */ export function canonicalizeJson(value) { if (value === null) return "null"; if (typeof value === "boolean") return value ? "true" : "false"; if (typeof value === "number") return JSON.stringify(value); if (typeof value === "string") return JSON.stringify(value); if (Array.isArray(value)) return "[" + value.map(canonicalizeJson).join(",") + "]"; if (typeof value === "object") { const obj = value; const keys = Object.keys(obj).sort(); const pairs = keys.map((k) => JSON.stringify(k) + ":" + canonicalizeJson(obj[k])); return "{" + pairs.join(",") + "}"; } throw new Error("canonicalizeJson: unsupported value type"); } /** * Build the canonical manifest object the gateway hashes: `{ files: [...] }` * with entries sorted by path and only the four expected keys per entry. * `content_type` defaults to `"application/octet-stream"` when absent. */ export function buildCanonicalManifest(entries) { const files = entries .map((e) => ({ path: e.path, sha256: e.sha256, size: e.size, content_type: e.content_type ?? "application/octet-stream", })) .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)); return { files }; } /** * Compute the hex SHA-256 of the canonical JSON encoding of a manifest. * Matches the gateway's `computeManifestDigest`. */ export async function computeManifestDigest(manifest) { const canonical = canonicalizeJson(manifest); const bytes = new TextEncoder().encode(canonical); const hash = await crypto.subtle.digest("SHA-256", bytes); return Array.from(new Uint8Array(hash)) .map((b) => b.toString(16).padStart(2, "0")) .join(""); } //# sourceMappingURL=canonicalize.js.map