@spfn/auth 0.3.0-beta.2 → 0.3.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -4
- package/dist/{authenticate-55LeXHqZ.d.ts → authenticate-Ctul07Sc.d.ts} +1 -1
- package/dist/client-proof.d.ts +23 -4
- package/dist/client-proof.js +57 -2
- package/dist/client-proof.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/nextjs/api.js +1 -1
- package/dist/nextjs/api.js.map +1 -1
- package/dist/nextjs/server.js +1 -1
- package/dist/nextjs/server.js.map +1 -1
- package/dist/server.d.ts +6 -5
- package/dist/server.js +58 -3
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/client-proof.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/client-proof/canonical-json.ts","../src/server/client-proof/proof.ts","../src/server/client-proof/refusal.ts","../src/server/client-proof/replay-store.ts","../src/server/client-proof/state.ts","../src/server/client-proof/admission.ts","../src/server/client-proof/contract-types.ts","../src/server/client-proof/dev-control.ts","../src/server/client-proof/contract-bundle.ts","../src/server/types.ts","../src/server/client-proof/wire-headers.ts","../src/server/client-proof/wire-version.ts","../src/server/client-proof/dev-handler.ts","../src/server/client-proof/refusal-response.ts","../src/server/client-proof/guard.ts","../src/server/client-proof/version-middleware.ts"],"sourcesContent":["/**\n * SPFN-CANON-JSON-1 — the canonical JSON form the mobile contract pins.\n *\n * The rules (contracts/mobile/spfn-mobile-contract.json `canonicalJson`):\n * - object keys sorted ascending by UTF-8 byte sequence\n * - no insignificant whitespace\n * - numbers are signed 64-bit integers only\n * - string escapes: `\"` and `\\` escaped; C0 controls use \\b \\f \\n \\r \\t where\n * defined and lowercase \\u00XX otherwise; every other scalar is emitted\n * literally as UTF-8\n * - absent optional fields are omitted, never null\n *\n * JSON.parse cannot implement this: it loses int64 precision, accepts duplicate\n * keys and (in V8) raw control characters, so both directions are hand-rolled.\n * A proof binds the received bytes — parse-then-re-encode equality is what makes\n * canonicity a rule a client can actually break.\n *\n * @module server/client-proof/canonical-json\n */\n\nexport type CanonicalObject = Map<string, CanonicalValue>;\n\nexport type CanonicalValue = null | boolean | bigint | string | CanonicalValue[] | CanonicalObject;\n\n/**\n * Parse failures carry the code the mobile conformance fixtures name\n * (Contracts/fixtures/canonical/rejects.json), so the fixtures can assert on it.\n */\nexport type CanonicalJsonErrorCode =\n | 'DUPLICATE_KEY'\n | 'NON_INTEGER_NUMBER'\n | 'TRAILING_CONTENT'\n | 'UNEXPECTED_END'\n | 'INVALID_TOKEN'\n | 'INVALID_ESCAPE'\n | 'INTEGER_OUT_OF_RANGE'\n | 'INVALID_UTF8';\n\nexport class CanonicalJsonError extends Error\n{\n constructor(readonly code: CanonicalJsonErrorCode)\n {\n super(`canonical JSON: ${code}`);\n this.name = 'CanonicalJsonError';\n }\n}\n\nconst INT64_MIN = -(2n ** 63n);\nconst INT64_MAX = 2n ** 63n - 1n;\n\n// ============================================================================\n// Parsing\n// ============================================================================\n\n/**\n * Parse bytes as SPFN-CANON-JSON-1.\n *\n * Arbitrary whitespace and key order are accepted here — parsing alone proves\n * nothing about canonicity. Callers that must enforce it re-encode the result\n * and compare bytes (see `isCanonicalBytes`).\n */\nexport function parseCanonicalJson(bytes: Uint8Array): CanonicalValue\n{\n let text: string;\n try\n {\n text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);\n }\n catch\n {\n throw new CanonicalJsonError('INVALID_UTF8');\n }\n\n const parser = new Parser(text);\n const value = parser.parseValue();\n parser.skipWhitespace();\n if (!parser.atEnd())\n {\n throw new CanonicalJsonError('TRAILING_CONTENT');\n }\n\n return value;\n}\n\n/** True when `bytes` are exactly the canonical encoding of the value they parse to. */\nexport function isCanonicalBytes(bytes: Uint8Array, value: CanonicalValue): boolean\n{\n const encoded = encodeCanonicalJson(value);\n if (encoded.length !== bytes.length)\n {\n return false;\n }\n for (let i = 0; i < encoded.length; i++)\n {\n if (encoded[i] !== bytes[i])\n {\n return false;\n }\n }\n\n return true;\n}\n\nclass Parser\n{\n private pos = 0;\n\n constructor(private readonly text: string) \n {}\n\n atEnd(): boolean\n {\n return this.pos >= this.text.length;\n }\n\n skipWhitespace(): void\n {\n while (!this.atEnd())\n {\n const c = this.text[this.pos];\n if (c === ' ' || c === '\\t' || c === '\\n' || c === '\\r')\n {\n this.pos++;\n continue;\n }\n break;\n }\n }\n\n parseValue(): CanonicalValue\n {\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const c = this.text[this.pos];\n if (c === '{')\n {\n return this.parseObject();\n }\n if (c === '[')\n {\n return this.parseArray();\n }\n if (c === '\"')\n {\n return this.parseString();\n }\n if (c === '-' || (c >= '0' && c <= '9'))\n {\n return this.parseNumber();\n }\n if (this.text.startsWith('null', this.pos))\n {\n this.pos += 4;\n\n return null;\n }\n if (this.text.startsWith('true', this.pos))\n {\n this.pos += 4;\n\n return true;\n }\n if (this.text.startsWith('false', this.pos))\n {\n this.pos += 5;\n\n return false;\n }\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n\n private parseObject(): CanonicalObject\n {\n this.pos++; // '{'\n const members: CanonicalObject = new Map();\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] === '}')\n {\n this.pos++;\n\n return members;\n }\n for (;;)\n {\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] !== '\"')\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n const key = this.parseString();\n if (members.has(key))\n {\n throw new CanonicalJsonError('DUPLICATE_KEY');\n }\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] !== ':')\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n this.pos++;\n members.set(key, this.parseValue());\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const next = this.text[this.pos];\n if (next === ',')\n {\n this.pos++;\n continue;\n }\n if (next === '}')\n {\n this.pos++;\n\n return members;\n }\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n }\n\n private parseArray(): CanonicalValue[]\n {\n this.pos++; // '['\n const items: CanonicalValue[] = [];\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] === ']')\n {\n this.pos++;\n\n return items;\n }\n for (;;)\n {\n items.push(this.parseValue());\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const next = this.text[this.pos];\n if (next === ',')\n {\n this.pos++;\n continue;\n }\n if (next === ']')\n {\n this.pos++;\n\n return items;\n }\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n }\n\n private parseString(): string\n {\n this.pos++; // '\"'\n let out = '';\n for (;;)\n {\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const c = this.text[this.pos];\n const code = this.text.charCodeAt(this.pos);\n if (c === '\"')\n {\n this.pos++;\n\n return out;\n }\n if (c === '\\\\')\n {\n out += this.parseEscape();\n continue;\n }\n if (code < 0x20)\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n out += c;\n this.pos++;\n }\n }\n\n private parseEscape(): string\n {\n this.pos++; // '\\'\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const c = this.text[this.pos];\n this.pos++;\n switch (c)\n {\n case '\"': return '\"';\n case '\\\\': return '\\\\';\n case '/': return '/';\n case 'b': return '\\b';\n case 'f': return '\\f';\n case 'n': return '\\n';\n case 'r': return '\\r';\n case 't': return '\\t';\n case 'u': return this.parseUnicodeEscape();\n default: throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n }\n\n private parseUnicodeEscape(): string\n {\n const high = this.readHex4();\n if (high >= 0xdc00 && high <= 0xdfff)\n {\n // A low surrogate with no preceding high surrogate.\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n if (high < 0xd800 || high > 0xdbff)\n {\n return String.fromCharCode(high);\n }\n // A high surrogate must be completed by an escaped low surrogate.\n if (this.text[this.pos] !== '\\\\' || this.text[this.pos + 1] !== 'u')\n {\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n this.pos += 2;\n const low = this.readHex4();\n if (low < 0xdc00 || low > 0xdfff)\n {\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n\n return String.fromCharCode(high, low);\n }\n\n private readHex4(): number\n {\n if (this.pos + 4 > this.text.length)\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const hex = this.text.slice(this.pos, this.pos + 4);\n if (!/^[0-9a-fA-F]{4}$/.test(hex))\n {\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n this.pos += 4;\n\n return parseInt(hex, 16);\n }\n\n private parseNumber(): bigint\n {\n const start = this.pos;\n if (this.text[this.pos] === '-')\n {\n this.pos++;\n }\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const first = this.text[this.pos];\n if (first < '0' || first > '9')\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n if (first === '0')\n {\n this.pos++;\n }\n else\n {\n while (!this.atEnd() && this.text[this.pos] >= '0' && this.text[this.pos] <= '9')\n {\n this.pos++;\n }\n }\n if (!this.atEnd())\n {\n const next = this.text[this.pos];\n if (next >= '0' && next <= '9')\n {\n // A leading zero followed by more digits.\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n if (next === '.' || next === 'e' || next === 'E')\n {\n throw new CanonicalJsonError('NON_INTEGER_NUMBER');\n }\n }\n const value = BigInt(this.text.slice(start, this.pos));\n if (value < INT64_MIN || value > INT64_MAX)\n {\n throw new CanonicalJsonError('INTEGER_OUT_OF_RANGE');\n }\n\n return value;\n }\n}\n\n// ============================================================================\n// Encoding\n// ============================================================================\n\n/** Encode a value as SPFN-CANON-JSON-1 bytes. */\nexport function encodeCanonicalJson(value: CanonicalValue): Uint8Array\n{\n return new TextEncoder().encode(encodeToString(value));\n}\n\nfunction encodeToString(value: CanonicalValue): string\n{\n if (value === null)\n {\n return 'null';\n }\n if (typeof value === 'boolean')\n {\n return value ? 'true' : 'false';\n }\n if (typeof value === 'bigint')\n {\n return value.toString();\n }\n if (typeof value === 'string')\n {\n return encodeString(value);\n }\n if (Array.isArray(value))\n {\n return `[${value.map(encodeToString).join(',')}]`;\n }\n const keys = [...value.keys()].sort(compareByCodePoints);\n const members = keys.map((key) => `${encodeString(key)}:${encodeToString(value.get(key)!)}`);\n\n return `{${members.join(',')}}`;\n}\n\n/**\n * UTF-8 byte order equals code point order, so keys are compared by code\n * points rather than UTF-16 code units (which would misorder U+E000..U+FFFF\n * against supplementary-plane characters).\n */\nfunction compareByCodePoints(a: string, b: string): number\n{\n let i = 0;\n let j = 0;\n while (i < a.length && j < b.length)\n {\n const ca = a.codePointAt(i)!;\n const cb = b.codePointAt(j)!;\n if (ca !== cb)\n {\n return ca - cb;\n }\n i += ca > 0xffff ? 2 : 1;\n j += cb > 0xffff ? 2 : 1;\n }\n\n return (a.length - i) - (b.length - j);\n}\n\nfunction encodeString(value: string): string\n{\n let out = '\"';\n for (const ch of value)\n {\n const code = ch.codePointAt(0)!;\n if (ch === '\"')\n {\n out += '\\\\\"';\n }\n else if (ch === '\\\\')\n {\n out += '\\\\\\\\';\n }\n else if (code === 0x08)\n {\n out += '\\\\b';\n }\n else if (code === 0x0c)\n {\n out += '\\\\f';\n }\n else if (code === 0x0a)\n {\n out += '\\\\n';\n }\n else if (code === 0x0d)\n {\n out += '\\\\r';\n }\n else if (code === 0x09)\n {\n out += '\\\\t';\n }\n else if (code < 0x20)\n {\n out += `\\\\u00${code.toString(16).padStart(2, '0')}`;\n }\n else\n {\n out += ch;\n }\n }\n\n return out + '\"';\n}\n","/**\n * SPFN-PROOF-INPUT-1 — proof-input assembly and verification for clientProofV1.\n *\n * The proof input is 8 fields joined by `\\n` in fixed order: profile, method,\n * path, clientId, keyId, nonce, issuedAtMillis, bodySha256. Any C0 control\n * character in any field is a hard refusal (the separator would otherwise be\n * ambiguous), never something to escape. The proof is an ECDSA P-256 signature\n * with SHA-256 over the canonical input's UTF-8 bytes, wire-encoded as the raw\n * `r ‖ s` 64 bytes in base16-lower (128 hex characters). DER is never accepted\n * on the wire: a platform signer that emits DER (Java `Signature`) converts to\n * raw before sending. Low-S normalization is not required — uniqueness is owned\n * by the nonce and replay window, so signature malleability cannot replay.\n *\n * @module server/client-proof/proof\n */\nimport { createHash, createPrivateKey, createPublicKey, sign, verify, type KeyObject } from 'node:crypto';\n\n/** The only auth profile this module implements. */\nexport const CLIENT_PROOF_PROFILE = 'clientProofV1';\n\n/** `bodySha256` when an operation carries no body: 64 zero characters. */\nexport const ABSENT_BODY_SHA256 = '0'.repeat(64);\n\n/** The contract's `clientProofV1.replayWindowMillis`. */\nexport const DEFAULT_REPLAY_WINDOW_MILLIS = 300_000;\n\n/** The eight proof-input fields, in the order the signature is taken over. */\nexport const PROOF_INPUT_FIELDS = [\n 'profile',\n 'method',\n 'path',\n 'clientId',\n 'keyId',\n 'nonce',\n 'issuedAtMillis',\n 'bodySha256',\n] as const;\n\n/** What joins the proof-input fields. */\nexport const PROOF_INPUT_SEPARATOR = '\\n';\n\n/** Raw `r ‖ s`: two 32-byte big-endian integers, always exactly this long. */\nexport const PROOF_SIGNATURE_BYTES = 64;\n\n/** The wire form is base16-lower of the raw signature: 128 hex characters. */\nexport const PROOF_SIGNATURE_HEX_LENGTH = PROOF_SIGNATURE_BYTES * 2;\n\n/**\n * Exactly 128 lowercase hex characters — anything else (DER, uppercase,\n * truncated, padded) is not a proof this contract describes.\n */\nconst PROOF_SIGNATURE_PATTERN = /^[0-9a-f]{128}$/;\n\n/**\n * Node's name for the fixed-width raw `r ‖ s` signature encoding. Both signing\n * and verification pin it, so the r/s padding rules (a 32-byte length is\n * guaranteed, a would-be 33-byte DER integer is trimmed) live inside\n * node:crypto rather than in a hand-rolled DER converter.\n */\nconst RAW_SIGNATURE_ENCODING = 'ieee-p1363';\n\ntype ProofInputField = (typeof PROOF_INPUT_FIELDS)[number];\n\nexport interface ClientProofInput\n{\n method: string;\n path: string;\n clientId: string;\n keyId: string;\n nonce: string;\n issuedAtMillis: bigint;\n bodySha256: string;\n}\n\n/** A C0 control character appeared in a proof field. */\nexport class ProofInputError extends Error\n{\n constructor()\n {\n super('proof input field contains a C0 control character');\n this.name = 'ProofInputError';\n }\n}\n\n/**\n * The canonical proof-input string the signature is taken over.\n *\n * @throws ProofInputError when any field contains a C0 control character.\n */\nexport function canonicalProofInput(input: ClientProofInput): string\n{\n const values: Record<ProofInputField, string> = {\n profile: CLIENT_PROOF_PROFILE,\n method: input.method,\n path: input.path,\n clientId: input.clientId,\n keyId: input.keyId,\n nonce: input.nonce,\n issuedAtMillis: input.issuedAtMillis.toString(),\n bodySha256: input.bodySha256,\n };\n const fields = PROOF_INPUT_FIELDS.map((name) => values[name]);\n for (const field of fields)\n {\n for (const ch of field)\n {\n if (ch.codePointAt(0)! < 0x20)\n {\n throw new ProofInputError();\n }\n }\n }\n\n return fields.join(PROOF_INPUT_SEPARATOR);\n}\n\n/**\n * The contract's public-key representation — SPKI DER, base64 (the same\n * representation `user_public_keys` and the web ES256 path store) — as a key\n * object. Anything that is not a P-256 EC key is refused at parse time, so a\n * key that could never verify a proof is never registered.\n *\n * @throws when the input is not base64 SPKI DER naming a P-256 key.\n */\nexport function parseClientProofPublicKey(spkiDerBase64: string): KeyObject\n{\n const key = createPublicKey({\n key: Buffer.from(spkiDerBase64, 'base64'),\n format: 'der',\n type: 'spki',\n });\n if (key.asymmetricKeyType !== 'ec' || key.asymmetricKeyDetails?.namedCurve !== 'prime256v1')\n {\n throw new Error('a clientProofV1 public key must be an ECDSA P-256 key');\n }\n\n return key;\n}\n\n/**\n * Verifies a presented proof against `input` and a registered public key.\n *\n * The input is assembled first, so a C0 control character throws no matter\n * what was presented — an unassemblable input is a contract violation, never\n * a proof answer. Then the wire-format gate: a value that is not exactly 128\n * lowercase hex characters — a DER signature, a truncated one, uppercase hex —\n * is invalid before any cryptography happens.\n *\n * @throws ProofInputError when an input field contains a C0 control character.\n */\nexport function verifyClientProof(input: ClientProofInput, presentedProof: string, publicKey: KeyObject): boolean\n{\n const data = Buffer.from(canonicalProofInput(input), 'utf8');\n if (!PROOF_SIGNATURE_PATTERN.test(presentedProof))\n {\n return false;\n }\n\n return verify(\n 'sha256',\n data,\n { key: publicKey, dsaEncoding: RAW_SIGNATURE_ENCODING },\n Buffer.from(presentedProof, 'hex'),\n );\n}\n\n/**\n * Signs `input` with a PKCS#8 DER base64 private key, producing the wire form\n * (raw `r ‖ s`, base16-lower).\n *\n * The verifying half's counterpart, here for tests and dev clients — a\n * production signer lives in the mobile SDKs against hardware-held keys.\n */\nexport function signClientProof(input: ClientProofInput, privateKeyPkcs8DerBase64: string): string\n{\n const key = createPrivateKey({\n key: Buffer.from(privateKeyPkcs8DerBase64, 'base64'),\n format: 'der',\n type: 'pkcs8',\n });\n\n return sign(\n 'sha256',\n Buffer.from(canonicalProofInput(input), 'utf8'),\n { key, dsaEncoding: RAW_SIGNATURE_ENCODING },\n ).toString('hex');\n}\n\n/** Lowercase base16 SHA-256 of `bytes`. */\nexport function sha256Hex(bytes: Uint8Array): string\n{\n return createHash('sha256').update(bytes).digest('hex');\n}\n","/**\n * Every way a clientProofV1 server refuses a request.\n *\n * The contract declares six error codes and forbids inventing a seventh, so\n * every refusal here is one of the six. Two rules decide which code a refusal\n * gets (mirroring the spfn-mobile reference server, the executable spec):\n *\n * 1. A refusal a new session could clear is an auth-family code (401). The SDK\n * re-handshakes exactly once on those.\n * 2. Everything else — the request is not the shape the contract describes —\n * is CONTRACT_UNSUPPORTED: the two ends do not agree on what the contract\n * is. PROOF_INVALID would provoke a pointless re-handshake and\n * PROFILE_REJECTED names one specific thing (a profile outside the\n * allowlist), used for exactly and only that.\n *\n * Every message is a fixed string: a message assembled from the request would\n * put a nonce, session id or body fragment into an error the client may log.\n *\n * @module server/client-proof/refusal\n */\nimport { randomBytes } from 'node:crypto';\n\nimport { encodeCanonicalJson, type CanonicalObject, type CanonicalValue } from './canonical-json';\n\n/** The six wire codes. The SDKs classify by code, never HTTP status. */\nexport type ClientProofErrorCode =\n | 'PROOF_INVALID'\n | 'PROOF_REPLAYED'\n | 'PROOF_EXPIRED'\n | 'SESSION_REVOKED'\n | 'PROFILE_REJECTED'\n | 'CONTRACT_UNSUPPORTED';\n\n/** The declaration order of the six codes — the contract export emits this order. */\nexport const CLIENT_PROOF_ERROR_CODES: readonly ClientProofErrorCode[] = [\n 'PROOF_INVALID',\n 'PROOF_REPLAYED',\n 'PROOF_EXPIRED',\n 'SESSION_REVOKED',\n 'PROFILE_REJECTED',\n 'CONTRACT_UNSUPPORTED',\n];\n\n/** The status each code answers with. The contract export reads this. */\nexport const HTTP_STATUS: Record<ClientProofErrorCode, number> = {\n PROOF_INVALID: 401,\n PROOF_REPLAYED: 401,\n PROOF_EXPIRED: 401,\n SESSION_REVOKED: 401,\n PROFILE_REJECTED: 400,\n CONTRACT_UNSUPPORTED: 409,\n};\n\n/** 128 random bits as lowercase base16 — request ids and control tokens. */\nexport function newHexId(): string\n{\n return randomBytes(16).toString('hex');\n}\n\nexport class ClientProofRefusal\n{\n constructor(\n readonly code: ClientProofErrorCode,\n readonly message: string,\n ) \n {}\n\n get httpStatus(): number\n {\n return HTTP_STATUS[this.code];\n }\n\n /** The canonical bytes of `{\"error\":{\"code\":…,\"message\":…,\"requestId\":…}}`. */\n envelopeBytes(requestId: string): Uint8Array\n {\n const error: CanonicalObject = new Map<string, CanonicalValue>([\n ['code', this.code],\n ['message', this.message],\n ['requestId', requestId],\n ]);\n\n return encodeCanonicalJson(new Map<string, CanonicalValue>([['error', error]]));\n }\n\n /** Nothing request-derived reaches a log through this. */\n toString(): string\n {\n return `ClientProofRefusal(${this.code})`;\n }\n\n // ---- shape: what arrived is not the contract (rule 2) -------------------\n\n static unroutable(): ClientProofRefusal\n {\n return contractViolation('no operation in this contract answers that method and path');\n }\n\n static malformedHeaders(): ClientProofRefusal\n {\n return contractViolation('the request does not carry the contract header fields exactly once each');\n }\n\n static missingContentType(): ClientProofRefusal\n {\n return contractViolation('a request that carries a body must declare the contract content type');\n }\n\n static bodyTooLarge(): ClientProofRefusal\n {\n return contractViolation('the request body exceeds the size this server accepts');\n }\n\n /**\n * The body parsed but its bytes are not the canonical form of what it\n * parsed to. Not PROOF_INVALID even though it is discovered next to the\n * proof: the proof over these bytes verifies perfectly well, and an\n * auth-family answer would tell the client to re-handshake and send the\n * same non-canonical bytes again.\n */\n static bodyNotCanonical(): ClientProofRefusal\n {\n return contractViolation('the request body is not the canonical JSON form of the value it encodes');\n }\n\n static bodyNotTheDeclaredType(): ClientProofRefusal\n {\n return contractViolation('the request body is not the request type this operation declares');\n }\n\n static sessionHeaderMisplaced(): ClientProofRefusal\n {\n return contractViolation('the session header is present exactly on the operations that require one');\n }\n\n static unprocessable(): ClientProofRefusal\n {\n return contractViolation('the request could not be processed');\n }\n\n /**\n * A client that ships separately from the server said nothing about which\n * contract it was built against. Without it the server cannot tell whether\n * the two ends agree, and answering as though they do is what produces the\n * undecodable body this check exists to replace.\n */\n static contractVersionMissing(): ClientProofRefusal\n {\n return contractViolation('a client of this kind must state the contract version it was generated from');\n }\n\n static contractVersionUnsupported(): ClientProofRefusal\n {\n return contractViolation('the stated contract version is outside the range this server serves');\n }\n\n // ---- the profile allowlist ----------------------------------------------\n\n static profileRejected(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROFILE_REJECTED', \"the named auth profile is not on this contract's allowlist\");\n }\n\n /**\n * A request that names a profile and presents Bearer credentials as well.\n * The profile named is a real one, so this is not a shape the two ends\n * disagree about: the request asked to be authenticated two ways at once\n * and the profile it named is the one refused.\n */\n static credentialsMixed(): ClientProofRefusal\n {\n return new ClientProofRefusal(\n 'PROFILE_REJECTED',\n 'an auth profile and Bearer credentials must not be mixed in one request',\n );\n }\n\n // ---- auth: a new session might clear it (rule 1) -------------------------\n\n static sessionRevoked(): ClientProofRefusal\n {\n return new ClientProofRefusal('SESSION_REVOKED', 'the key or session was revoked');\n }\n\n static proofExpired(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROOF_EXPIRED', 'issuedAtMillis falls outside the replay window');\n }\n\n static proofReplayed(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROOF_REPLAYED', 'the nonce was already used inside the replay window');\n }\n\n static proofInvalid(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROOF_INVALID', 'the client proof did not verify');\n }\n}\n\nfunction contractViolation(message: string): ClientProofRefusal\n{\n return new ClientProofRefusal('CONTRACT_UNSUPPORTED', message);\n}\n","/**\n * The clientProofV1 replay ledger as a pluggable store — the same pattern\n * one-time-token uses: an in-memory default, an opt-in Redis/Valkey store on\n * top of `getCache()`, and a module-level configuration hook.\n *\n * One data structure owns the memory semantics: `MemoryReplayLedger` is used\n * synchronously by the dev surface's `ClientProofState` (whose `admit` must\n * stay synchronous to keep its single-thread atomicity argument) and wrapped\n * by `MemoryReplayStore` for the async middleware path. There is exactly one\n * implementation of \"spent inside the window\", not two.\n *\n * The nonce-spending rule is the contract's: a nonce is recorded only when a\n * request is admitted, so `isSpent` (the replay-order check) and `spend` (the\n * post-verification record) are separate calls. `spend` is check-and-set — it\n * answers false when another request spent the nonce between the two calls —\n * so the race two concurrent same-nonce requests can open is closed at the\n * store, for memory and Redis alike.\n *\n * Store failure is the caller's refusal, never a pass-through: the middleware\n * path treats a throwing store as \"reject the request\" (fail-closed). An auth\n * surface does not fail open.\n *\n * @module server/client-proof/replay-store\n */\nimport { getCache } from '@spfn/core/cache';\n\nimport { DEFAULT_REPLAY_WINDOW_MILLIS, sha256Hex } from './proof';\n\n/**\n * The ledger key. `JSON.stringify` of the pair, so no crafted clientId/nonce\n * concatenation can collide with another pair — the fields are checked for C0\n * controls only later, at proof verification, so the key must be unambiguous\n * for arbitrary strings.\n */\nexport function replayLedgerKey(clientId: string, nonce: string): string\n{\n return JSON.stringify([clientId, nonce]);\n}\n\n/**\n * What the middleware's replay ledger must answer. Both methods may reject;\n * the caller refuses the request when they do (fail-closed).\n */\nexport interface ClientProofReplayStore\n{\n /** True when (clientId, nonce) was already spent inside the window. */\n isSpent(clientId: string, nonce: string): Promise<boolean>;\n\n /**\n * Records the pair as spent. False when it was already spent — the caller\n * lost a race and must answer PROOF_REPLAYED, not accept twice.\n */\n spend(clientId: string, nonce: string): Promise<boolean>;\n}\n\n/**\n * The in-memory ledger — the single implementation of the window semantics.\n *\n * Entries carry the millisecond they were recorded at; `prune` drops an entry\n * only once a proof carrying that timestamp would be refused as expired\n * anyway (the exact negation of the admission window check). All methods are\n * synchronous so `ClientProofState.admit` can stay atomic on Node's single\n * thread.\n */\nexport class MemoryReplayLedger\n{\n /** replayLedgerKey(...) → the millis it was spent at. */\n private readonly spent = new Map<string, number>();\n\n isSpent(clientId: string, nonce: string): boolean\n {\n return this.spent.has(replayLedgerKey(clientId, nonce));\n }\n\n /** Records the pair at `atMillis`; false when it was already spent. */\n spend(clientId: string, nonce: string, atMillis: number): boolean\n {\n const key = replayLedgerKey(clientId, nonce);\n if (this.spent.has(key))\n {\n return false;\n }\n this.spent.set(key, atMillis);\n\n return true;\n }\n\n /** Drops entries older than the window, judged against `nowMillis`. */\n prune(nowMillis: number, windowMillis: number): void\n {\n for (const [key, spentAtMillis] of this.spent)\n {\n if (nowMillis - spentAtMillis > windowMillis)\n {\n this.spent.delete(key);\n }\n }\n }\n\n get size(): number\n {\n return this.spent.size;\n }\n\n clear(): void\n {\n this.spent.clear();\n }\n}\n\n/**\n * The default store: a process-local `MemoryReplayLedger` on the wall clock.\n *\n * Correct for a single process. Behind a multi-instance deployment each\n * instance keeps its own ledger, so a replay against a *different* instance\n * is not seen — that deployment opts into `RedisReplayStore`.\n */\nexport class MemoryReplayStore implements ClientProofReplayStore\n{\n private readonly ledger = new MemoryReplayLedger();\n\n constructor(private readonly windowMillis: number = DEFAULT_REPLAY_WINDOW_MILLIS)\n {}\n\n async isSpent(clientId: string, nonce: string): Promise<boolean>\n {\n this.ledger.prune(Date.now(), this.windowMillis);\n\n return this.ledger.isSpent(clientId, nonce);\n }\n\n async spend(clientId: string, nonce: string): Promise<boolean>\n {\n const now = Date.now();\n this.ledger.prune(now, this.windowMillis);\n\n return this.ledger.spend(clientId, nonce, now);\n }\n}\n\n/**\n * The opt-in shared ledger over `getCache()` (ioredis): `SET NX PX <window>`.\n *\n * The key hashes the pair, so arbitrary clientId/nonce strings become short,\n * safe Redis keys with no ambiguity. `PX` makes Redis expire the entry itself\n * exactly when a proof reusing the nonce would pass the window check again.\n *\n * Fail-closed by construction: when the cache is not configured or a command\n * rejects, the error propagates and the caller refuses the request. Nothing\n * here answers \"not spent\" on a store it could not reach.\n */\nexport class RedisReplayStore implements ClientProofReplayStore\n{\n constructor(private readonly windowMillis: number = DEFAULT_REPLAY_WINDOW_MILLIS)\n {}\n\n async isSpent(clientId: string, nonce: string): Promise<boolean>\n {\n return await this.cache().exists(this.key(clientId, nonce)) === 1;\n }\n\n async spend(clientId: string, nonce: string): Promise<boolean>\n {\n return await this.cache().set(this.key(clientId, nonce), '1', 'PX', this.windowMillis, 'NX') === 'OK';\n }\n\n private cache(): NonNullable<ReturnType<typeof getCache>>\n {\n const cache = getCache();\n if (!cache)\n {\n throw new Error('client-proof replay ledger: cache is not available');\n }\n\n return cache;\n }\n\n private key(clientId: string, nonce: string): string\n {\n return `spfn:auth:client-proof:replay:${sha256Hex(Buffer.from(replayLedgerKey(clientId, nonce), 'utf8'))}`;\n }\n}\n\n// ---- module-level configuration (the one-time-token pattern) ---------------\n\nlet configured: ClientProofReplayStore | null = null;\n\n/**\n * Installs the replay store the authenticate middleware uses. Pass\n * `new RedisReplayStore()` to opt into the shared ledger; pass null to return\n * to the in-memory default.\n */\nexport function configureClientProofReplayStore(store: ClientProofReplayStore | null): void\n{\n configured = store;\n}\n\n/** The configured store, or a lazily created in-memory default. */\nexport function getClientProofReplayStore(): ClientProofReplayStore\n{\n configured ??= new MemoryReplayStore();\n\n return configured;\n}\n","/**\n * Everything a clientProofV1 server remembers between requests: issued\n * sessions, the replay ledger, revoked keys and the key directory.\n *\n * The admission order is the contract's, not this file's invention\n * (`clientProofV1.revocationRule` + the replay fixtures):\n *\n * 1. revoked keyId / invalid session → SESSION_REVOKED — before proof\n * verification, so revocation stays distinguishable from a bad proof;\n * 2. issuedAtMillis outside the replay window (0 <= age <= window) → PROOF_EXPIRED;\n * 3. a repeated (clientId, nonce) pair inside the window → PROOF_REPLAYED;\n * 4. only then signature verification → PROOF_INVALID when it does not verify.\n *\n * A nonce is recorded as spent only on admission: a request refused for any\n * earlier reason has not spent anything, so a client that fixes the reason and\n * retries with the same nonce is not punished twice for one mistake. This is\n * why core's `NonceStore.checkAndSet` (which records on check) is not reused\n * here — its semantics would spend a nonce on a refused request.\n *\n * `admit` is synchronous, so on Node's single thread the whole sequence is\n * atomic: two requests presenting the same nonce cannot interleave inside it.\n *\n * @module server/client-proof/state\n */\nimport type { KeyObject } from 'node:crypto';\n\nimport {\n DEFAULT_REPLAY_WINDOW_MILLIS,\n parseClientProofPublicKey,\n verifyClientProof,\n type ClientProofInput,\n} from './proof';\nimport { ClientProofRefusal, newHexId } from './refusal';\nimport { MemoryReplayLedger } from './replay-store';\n\n/** Millisecond clock. Injectable so expiry paths are testable without waiting. */\nexport interface ClientProofClock\n{\n nowMillis(): number;\n}\n\nexport function systemClock(): ClientProofClock\n{\n return { nowMillis: () => Date.now() };\n}\n\n/** A clock a test (or the dev control surface) can move forward. */\nexport class TestClock implements ClientProofClock\n{\n constructor(private millis: number) \n {}\n\n nowMillis(): number\n {\n return this.millis;\n }\n\n advance(byMillis: number): void\n {\n this.millis += byMillis;\n }\n}\n\n/** What `stats()` reports. Counters only; nothing a request carried. */\nexport interface ClientProofStats\n{\n requestCount: number;\n handshakeCount: number;\n echoCount: number;\n itemsListCount: number;\n refusalCount: number;\n liveSessionCount: number;\n spentNonceCount: number;\n}\n\ninterface ClientProofSession\n{\n clientId: string;\n keyId: string;\n expiresAtMillis: number;\n}\n\ninterface PathHold\n{\n millis: number;\n remaining: number;\n}\n\nexport interface ClientProofStateOptions\n{\n /**\n * keyId → registered public key, as SPKI DER base64. The private half\n * never reaches the server: a client generates its keypair (hardware-held\n * on mobile) and only the public key is registered — at construction here,\n * or later through `registerPublicKey` (the dev `/control/register-key`\n * route).\n */\n publicKeys: Record<string, string>;\n\n clock?: ClientProofClock;\n\n /** @default 600000 */\n sessionTtlMillis?: number;\n\n /** The contract's replay window. @default 300000 */\n replayWindowMillis?: number;\n}\n\nexport const DEFAULT_SESSION_TTL_MILLIS = 600_000;\n\nexport class ClientProofState\n{\n readonly replayWindowMillis: number;\n\n private readonly clock: ClientProofClock;\n private readonly initialPublicKeys: ReadonlyMap<string, KeyObject>;\n private readonly publicKeys = new Map<string, KeyObject>();\n private readonly sessions = new Map<string, ClientProofSession>();\n\n /** The replay ledger — the shared memory implementation, used dev-only here. */\n private readonly spentNonces = new MemoryReplayLedger();\n\n private readonly revokedKeyIds = new Set<string>();\n private readonly holds = new Map<string, PathHold>();\n\n private readonly initialSessionTtlMillis: number;\n private sessionTtlMillis: number;\n\n private requestCount = 0;\n private handshakeCount = 0;\n private echoCount = 0;\n private itemsListCount = 0;\n private refusalCount = 0;\n\n constructor(options: ClientProofStateOptions)\n {\n this.clock = options.clock ?? systemClock();\n this.initialSessionTtlMillis = options.sessionTtlMillis ?? DEFAULT_SESSION_TTL_MILLIS;\n this.sessionTtlMillis = this.initialSessionTtlMillis;\n this.replayWindowMillis = options.replayWindowMillis ?? DEFAULT_REPLAY_WINDOW_MILLIS;\n // Parsed once here, so a key that is not P-256 SPKI fails loudly at\n // construction rather than as a PROOF_INVALID mystery at request time.\n this.initialPublicKeys = new Map(\n Object.entries(options.publicKeys).map(([keyId, spki]) => [keyId, parseClientProofPublicKey(spki)]),\n );\n for (const [keyId, key] of this.initialPublicKeys)\n {\n this.publicKeys.set(keyId, key);\n }\n }\n\n // ---- key registration --------------------------------------------------\n\n /**\n * Registers (or replaces) the public key `keyId` presents proofs under.\n *\n * @throws when the key is not base64 SPKI DER naming a P-256 key.\n */\n registerPublicKey(keyId: string, publicKeySpkiDerBase64: string): void\n {\n this.publicKeys.set(keyId, parseClientProofPublicKey(publicKeySpkiDerBase64));\n }\n\n // ---- admission ---------------------------------------------------------\n\n /**\n * Runs the contract's checks in the contract's order and returns the\n * refusal, or null when the request is admitted (spending its nonce).\n */\n admit(args: {\n clientId: string;\n keyId: string;\n presentedSessionId: string | null;\n requiresSession: boolean;\n proofInput: ClientProofInput;\n presentedProof: string;\n }): ClientProofRefusal | null\n {\n const now = this.clock.nowMillis();\n this.prune(now);\n\n // 1. Revocation, before anything the proof could explain. A revoked key\n // and a dropped session are the same answer on purpose: both are\n // cleared by opening a new session.\n if (this.revokedKeyIds.has(args.keyId))\n {\n return ClientProofRefusal.sessionRevoked();\n }\n if (args.requiresSession)\n {\n const session = args.presentedSessionId === null ? undefined : this.sessions.get(args.presentedSessionId);\n if (session === undefined || session.expiresAtMillis <= now\n || session.keyId !== args.keyId || session.clientId !== args.clientId)\n {\n return ClientProofRefusal.sessionRevoked();\n }\n }\n\n // 2. The replay window, judged against this server's clock.\n const age = now - Number(args.proofInput.issuedAtMillis);\n if (age < 0 || age > this.replayWindowMillis)\n {\n return ClientProofRefusal.proofExpired();\n }\n\n // 3. One acceptance per (clientId, nonce) inside that window.\n if (this.spentNonces.isSpent(args.clientId, args.proofInput.nonce))\n {\n return ClientProofRefusal.proofReplayed();\n }\n\n // 4. The proof itself, last, so the three answers above stay\n // distinguishable. An unregistered keyId lands here rather than in\n // step 1, and shares PROOF_INVALID with a failed signature: it was\n // never registered, so it was never revoked, there is nothing for a\n // new session to fix, and whether a keyId exists is not inferable\n // from the refusal — the same non-disclosure the revocation rule\n // keeps.\n const publicKey = this.publicKeys.get(args.keyId);\n if (publicKey === undefined)\n {\n return ClientProofRefusal.proofInvalid();\n }\n if (!verifyClientProof(args.proofInput, args.presentedProof, publicKey))\n {\n return ClientProofRefusal.proofInvalid();\n }\n\n this.spentNonces.spend(args.clientId, args.proofInput.nonce, Number(args.proofInput.issuedAtMillis));\n\n return null;\n }\n\n // ---- sessions ----------------------------------------------------------\n\n /** Opens a session and returns its id and the expiry the server advertises. */\n openSession(clientId: string, keyId: string): { sessionId: string; expiresAtMillis: number }\n {\n const now = this.clock.nowMillis();\n this.prune(now);\n const sessionId = newHexId();\n const expiresAtMillis = now + this.sessionTtlMillis;\n this.sessions.set(sessionId, { clientId, keyId, expiresAtMillis });\n\n return { sessionId, expiresAtMillis };\n }\n\n /** Test hook: installs a session with a chosen id (wire-fixture replays). */\n seedSession(sessionId: string, clientId: string, keyId: string, expiresAtMillis: number): void\n {\n this.sessions.set(sessionId, { clientId, keyId, expiresAtMillis });\n }\n\n /** Drops every session, as a restart would. Advertised expiries stay told. */\n expireSessions(): void\n {\n this.sessions.clear();\n }\n\n /** Revokes a key and drops the sessions it opened. */\n revokeKey(keyId: string): void\n {\n this.revokedKeyIds.add(keyId);\n for (const [sessionId, session] of this.sessions)\n {\n if (session.keyId === keyId)\n {\n this.sessions.delete(sessionId);\n }\n }\n }\n\n setSessionTtlMillis(millis: number): void\n {\n this.sessionTtlMillis = millis;\n }\n\n /** Returns the state to how it started, counters and registered keys included. */\n reset(): void\n {\n this.publicKeys.clear();\n for (const [keyId, key] of this.initialPublicKeys)\n {\n this.publicKeys.set(keyId, key);\n }\n this.sessions.clear();\n this.spentNonces.clear();\n this.revokedKeyIds.clear();\n this.holds.clear();\n this.sessionTtlMillis = this.initialSessionTtlMillis;\n this.requestCount = 0;\n this.handshakeCount = 0;\n this.echoCount = 0;\n this.itemsListCount = 0;\n this.refusalCount = 0;\n }\n\n // ---- delays (dev/test only) --------------------------------------------\n\n /** Makes the next `count` requests to `path` wait `millis` before processing. */\n holdPath(path: string, millis: number, count: number): void\n {\n this.holds.set(path, { millis, remaining: count });\n }\n\n /** Consumes one configured delay for `path`; returns how long to wait, or 0. */\n takeHoldMillis(path: string): number\n {\n const hold = this.holds.get(path);\n if (hold === undefined)\n {\n return 0;\n }\n hold.remaining -= 1;\n if (hold.remaining <= 0)\n {\n this.holds.delete(path);\n }\n\n return hold.millis;\n }\n\n // ---- counters ----------------------------------------------------------\n\n recordRequest(): void\n {\n this.requestCount += 1;\n }\n\n recordOperation(operationId: string): void\n {\n if (operationId === 'auth.clientProof.handshake')\n {\n this.handshakeCount += 1;\n }\n else if (operationId === 'echo.send')\n {\n this.echoCount += 1;\n }\n else if (operationId === 'items.list')\n {\n this.itemsListCount += 1;\n }\n }\n\n recordRefusal(): void\n {\n this.refusalCount += 1;\n }\n\n stats(): ClientProofStats\n {\n this.prune(this.clock.nowMillis());\n\n return {\n requestCount: this.requestCount,\n handshakeCount: this.handshakeCount,\n echoCount: this.echoCount,\n itemsListCount: this.itemsListCount,\n refusalCount: this.refusalCount,\n liveSessionCount: this.sessions.size,\n spentNonceCount: this.spentNonces.size,\n };\n }\n\n nowMillis(): number\n {\n return this.clock.nowMillis();\n }\n\n /** The clock, exposed for the dev control surface's advance-clock route. */\n get clockRef(): ClientProofClock\n {\n return this.clock;\n }\n\n // ---- housekeeping ------------------------------------------------------\n\n /**\n * Drops what can no longer affect an answer. The nonce predicate is the\n * exact negation of the window check in `admit`: an entry is dropped only\n * once a proof carrying that issuedAtMillis would be refused as expired\n * anyway. Dropping one moment earlier would let a nonce inside the window\n * be spent twice.\n */\n private prune(nowMillis: number): void\n {\n for (const [sessionId, session] of this.sessions)\n {\n if (session.expiresAtMillis <= nowMillis)\n {\n this.sessions.delete(sessionId);\n }\n }\n this.spentNonces.prune(nowMillis, this.replayWindowMillis);\n }\n}\n","/**\n * The checks between a clientProofV1 request arriving and being applied.\n *\n * Shape first, then the profile allowlist, then the proof. That order is\n * forced: none of the proof checks can run until the fields they read are\n * known to be present and the body is known to be the bytes the digest is\n * supposed to cover. The order *inside* the proof checks is the contract's and\n * lives in `ClientProofState.admit`.\n *\n * @module server/client-proof/admission\n */\nimport { isCanonicalBytes, parseCanonicalJson, type CanonicalValue } from './canonical-json';\nimport { CLIENT_PROOF_PROFILE, sha256Hex, type ClientProofInput } from './proof';\nimport { ClientProofRefusal } from './refusal';\nimport type { ClientProofState } from './state';\n\n/** D23 wire-header names, ratified as proposed by the mobile dev bundle. */\nexport const CLIENT_PROOF_HEADERS = {\n profile: 'x-spfn-auth-profile',\n clientId: 'x-spfn-client-id',\n keyId: 'x-spfn-key-id',\n nonce: 'x-spfn-nonce',\n issuedAtMillis: 'x-spfn-issued-at',\n proof: 'x-spfn-proof',\n session: 'x-spfn-session',\n} as const;\n\nexport const CLIENT_PROOF_CONTENT_TYPE = 'application/json';\n\nconst INT64_MIN = -(2n ** 63n);\nconst INT64_MAX = 2n ** 63n - 1n;\n\n/** The contract header fields one request presented. */\nexport interface ClientProofCredentials\n{\n profile: string;\n clientId: string;\n keyId: string;\n nonce: string;\n issuedAtMillis: bigint;\n proof: string;\n sessionId: string | null;\n}\n\nexport type Admission =\n | { admitted: false; refusal: ClientProofRefusal }\n | { admitted: true; value: CanonicalValue; credentials: ClientProofCredentials };\n\n/**\n * Runs every check for one operation over already-read body bytes.\n *\n * `path` must be the operation's contract path (what the client signed), not a\n * proxied or rewritten one.\n */\nexport function admitClientProofRequest(args: {\n state: ClientProofState;\n headers: Headers;\n method: string;\n path: string;\n requiresSession: boolean;\n body: Uint8Array;\n}): Admission\n{\n const credentials = readCredentials(args.headers);\n if (credentials === null)\n {\n return refused(ClientProofRefusal.malformedHeaders());\n }\n if (credentials.profile !== CLIENT_PROOF_PROFILE)\n {\n return refused(ClientProofRefusal.profileRejected());\n }\n if (!isRequestContentType(args.headers.get('content-type')))\n {\n return refused(ClientProofRefusal.missingContentType());\n }\n if (args.requiresSession !== (credentials.sessionId !== null))\n {\n return refused(ClientProofRefusal.sessionHeaderMisplaced());\n }\n\n let value: CanonicalValue;\n try\n {\n value = parseCanonicalJson(args.body);\n }\n catch\n {\n return refused(ClientProofRefusal.bodyNotCanonical());\n }\n // The proof binds the received bytes; accepting a re-serialization would\n // let two implementations disagree about what was signed.\n if (!isCanonicalBytes(args.body, value))\n {\n return refused(ClientProofRefusal.bodyNotCanonical());\n }\n\n const proofInput: ClientProofInput = {\n method: args.method,\n path: args.path,\n clientId: credentials.clientId,\n keyId: credentials.keyId,\n nonce: credentials.nonce,\n issuedAtMillis: credentials.issuedAtMillis,\n bodySha256: sha256Hex(args.body),\n };\n\n let refusal: ClientProofRefusal | null;\n try\n {\n refusal = args.state.admit({\n clientId: credentials.clientId,\n keyId: credentials.keyId,\n presentedSessionId: credentials.sessionId,\n requiresSession: args.requiresSession,\n proofInput,\n presentedProof: credentials.proof,\n });\n }\n catch\n {\n // A C0 control character in a header field makes the proof input\n // unassemblable — the request is not the shape the contract describes.\n return refused(ClientProofRefusal.unprocessable());\n }\n if (refusal !== null)\n {\n return refused(refusal);\n }\n\n return { admitted: true, value, credentials };\n}\n\nfunction refused(refusal: ClientProofRefusal): Admission\n{\n return { admitted: false, refusal };\n}\n\n/**\n * The contract header fields, or null when any is absent or malformed.\n *\n * Fetch `Headers` folds a repeated field into one comma-joined value, so\n * \"sent more than once\" is not directly observable here; a folded value fails\n * either the issuedAt grammar or proof verification instead.\n *\n * Exported for the authenticate middleware's profile path, which runs the\n * same shape checks over arbitrary routes.\n */\nexport function readCredentials(headers: Headers): ClientProofCredentials | null\n{\n const profile = headers.get(CLIENT_PROOF_HEADERS.profile);\n const clientId = headers.get(CLIENT_PROOF_HEADERS.clientId);\n const keyId = headers.get(CLIENT_PROOF_HEADERS.keyId);\n const nonce = headers.get(CLIENT_PROOF_HEADERS.nonce);\n const issuedAtRaw = headers.get(CLIENT_PROOF_HEADERS.issuedAtMillis);\n const proof = headers.get(CLIENT_PROOF_HEADERS.proof);\n if (profile === null || clientId === null || keyId === null\n || nonce === null || issuedAtRaw === null || proof === null)\n {\n return null;\n }\n const issuedAtMillis = parseInt64(issuedAtRaw);\n if (issuedAtMillis === null)\n {\n return null;\n }\n\n return {\n profile,\n clientId,\n keyId,\n nonce,\n issuedAtMillis,\n proof,\n sessionId: headers.get(CLIENT_PROOF_HEADERS.session),\n };\n}\n\nfunction parseInt64(raw: string): bigint | null\n{\n if (!/^[+-]?\\d{1,19}$/.test(raw))\n {\n return null;\n }\n const value = BigInt(raw);\n if (value < INT64_MIN || value > INT64_MAX)\n {\n return null;\n }\n\n return value;\n}\n\n/** Exported for the authenticate middleware's profile path. */\nexport function isRequestContentType(value: string | null): boolean\n{\n if (value === null)\n {\n return false;\n }\n\n return value.split(';')[0].trim().toLowerCase() === CLIENT_PROOF_CONTENT_TYPE;\n}\n","/**\n * The mobile dev-contract types and operations, decoded from / encoded to\n * canonical values. Strict on purpose: a missing required field, a wrong type\n * or an unknown field is \"not the request type this operation declares\".\n *\n * This module is the source of truth for `operations`. The exported contract\n * bundle (`contracts/mobile/spfn-mobile-contract.json`) is generated from it\n * by `contract-bundle.ts`; spfn-mobile consumes that export rather than the\n * other way round.\n *\n * @module server/client-proof/contract-types\n */\nimport type { CanonicalObject, CanonicalValue } from './canonical-json';\n\nexport interface ContractOperation\n{\n id:\n | 'auth.clientProof.handshake'\n | 'echo.send'\n | 'items.list'\n | 'auth.enroll.register'\n | 'auth.enroll.login'\n | 'auth.enroll.oauthNative'\n | 'auth.keys.rotate'\n | 'auth.keys.list'\n | 'auth.keys.revoke'\n | 'auth.keys.revokeAll';\n method: 'POST';\n path: string;\n\n /**\n * How a call is admitted. `clientProofV1` operations run the proof\n * admission order; `none` operations are the unproven class — accepted\n * with neither proof headers nor a session header, because enrollment is\n * called before any key exists to sign with.\n */\n authProfile: 'clientProofV1' | 'none';\n requiresSession: boolean;\n requestType: string;\n responseType: string;\n summary: string;\n\n /**\n * The contract version this operation first appeared in. Required, so an\n * operation added later cannot ship without one: omitting it is a compile\n * error rather than a hole a consumer discovers.\n *\n * It is history, not policy. This contract's compatibility policy is\n * `allOrNothing` — one version passes or refuses the whole surface — so\n * nothing here changes a verdict. It exists so a deprecation has somewhere\n * to be recorded, and as the precedent an app contract's `perOperation`\n * policy reads.\n */\n since: string;\n\n /**\n * The contract version that marked this operation deprecated, if one has.\n * A deprecated operation is still served: the mark is the notice that opens\n * the grace period before removal.\n */\n deprecatedIn?: string;\n\n /**\n * The contract version that removed this operation, if one has.\n *\n * A removed operation leaves this list, so nothing here carries the field\n * today. When the first removal happens, `removedIn` is where the fact is\n * recorded — how a removed operation stays visible after leaving the list\n * is decided then, not invented in advance.\n */\n removedIn?: string;\n}\n\nexport const CONTRACT_OPERATIONS: readonly ContractOperation[] = [\n {\n id: 'auth.clientProof.handshake',\n method: 'POST',\n path: '/v1/auth/client-proof/handshake',\n authProfile: 'clientProofV1',\n requiresSession: false,\n requestType: 'HandshakeRequest',\n responseType: 'HandshakeResponse',\n summary: 'Presents a client proof and opens a session.',\n since: '0.1.0',\n },\n {\n id: 'echo.send',\n method: 'POST',\n path: '/v1/echo',\n authProfile: 'clientProofV1',\n requiresSession: true,\n requestType: 'EchoRequest',\n responseType: 'EchoResponse',\n summary: 'Authenticated round trip used as the smallest real vertical slice.',\n since: '0.1.0',\n },\n {\n id: 'items.list',\n method: 'POST',\n path: '/v1/items/list',\n authProfile: 'clientProofV1',\n requiresSession: true,\n requestType: 'ListItemsRequest',\n responseType: 'ListItemsResponse',\n summary: 'Authenticated paged read covering optional fields and arrays.',\n since: '0.1.0',\n },\n];\n\n/**\n * The `/_auth` surface exported into the mobile contract: enrollment, login\n * and key rotation. These are ordinary SPFN REST routes, not canonical-JSON\n * operations — the dev handler never serves them, and their wire rules are\n * the `restOperations` section of the bundle, not `canonicalJson`.\n *\n * The three `authProfile: 'none'` operations are the unproven class: they are\n * accepted with neither proof headers nor a session header, because they are\n * how a client obtains a key in the first place. `auth.keys.rotate` requires\n * an authenticated caller (a clientProofV1 proof on this surface); an\n * unproven call to it is refused like any failed admission.\n */\nexport const AUTH_SURFACE_OPERATIONS: readonly ContractOperation[] = [\n {\n id: 'auth.enroll.register',\n method: 'POST',\n path: '/_auth/register',\n authProfile: 'none',\n requiresSession: false,\n requestType: 'RegisterRequest',\n responseType: 'RegisterResponse',\n summary: 'Registers an account with a verification token and enrolls the client-generated public key.',\n since: '0.3.0',\n },\n {\n id: 'auth.enroll.login',\n method: 'POST',\n path: '/_auth/login',\n authProfile: 'none',\n requiresSession: false,\n requestType: 'LoginRequest',\n responseType: 'LoginResponse',\n summary: 'Authenticates with password credentials and enrolls a fresh client-generated public key.',\n since: '0.3.0',\n },\n {\n id: 'auth.enroll.oauthNative',\n method: 'POST',\n path: '/_auth/oauth/{provider}/native',\n authProfile: 'none',\n requiresSession: false,\n requestType: 'OauthNativeRequest',\n responseType: 'OauthNativeResponse',\n summary: 'Verifies a native/web social id_token server-side and enrolls the client-generated public key.',\n since: '0.3.0',\n },\n {\n id: 'auth.keys.rotate',\n method: 'POST',\n path: '/_auth/keys/rotate',\n authProfile: 'clientProofV1',\n requiresSession: false,\n requestType: 'RotateKeyRequest',\n responseType: 'RotateKeyResponse',\n summary: 'Replaces the authenticated key with a new client-generated public key before its TTL runs out.',\n since: '0.3.0',\n },\n {\n id: 'auth.keys.list',\n method: 'POST',\n path: '/_auth/keys/list',\n authProfile: 'clientProofV1',\n requiresSession: false,\n requestType: 'ListKeysRequest',\n responseType: 'ListKeysResponse',\n summary: 'Lists the keys registered to the caller, one per device that can sign for them.',\n since: '0.4.1',\n },\n {\n id: 'auth.keys.revoke',\n method: 'POST',\n path: '/_auth/keys/revoke',\n authProfile: 'clientProofV1',\n requiresSession: false,\n requestType: 'RevokeKeyRequest',\n responseType: 'RevokeKeyResponse',\n summary: 'Revokes one of the caller\\'s keys, signing that device out.',\n since: '0.4.1',\n },\n {\n id: 'auth.keys.revokeAll',\n method: 'POST',\n path: '/_auth/keys/revoke-all',\n authProfile: 'clientProofV1',\n requiresSession: false,\n requestType: 'RevokeAllKeysRequest',\n responseType: 'RevokeAllKeysResponse',\n summary: 'Revokes every key the caller has, sparing the calling device unless asked otherwise.',\n since: '0.4.1',\n },\n];\n\n/** The body is canonical JSON but not the declared request type. */\nexport class ContractTypeError extends Error\n{\n constructor()\n {\n super('not the declared contract type');\n this.name = 'ContractTypeError';\n }\n}\n\nexport interface HandshakeRequest\n{\n clientId: string;\n keyId: string;\n nonce: string;\n issuedAtMillis: bigint;\n}\n\nexport interface EchoRequest\n{\n message: string;\n sequence: bigint;\n}\n\nexport interface ListItemsRequest\n{\n limit: bigint;\n cursor?: string;\n}\n\nexport interface ContractItem\n{\n id: string;\n name: string;\n updatedAtMillis: bigint;\n}\n\n// ============================================================================\n// Decoding\n// ============================================================================\n\nexport function decodeHandshakeRequest(value: CanonicalValue): HandshakeRequest\n{\n const members = objectWithKeys(value, ['clientId', 'keyId', 'nonce', 'issuedAtMillis'], []);\n\n return {\n clientId: text(members.get('clientId')),\n keyId: text(members.get('keyId')),\n nonce: text(members.get('nonce')),\n issuedAtMillis: integer(members.get('issuedAtMillis')),\n };\n}\n\nexport function decodeEchoRequest(value: CanonicalValue): EchoRequest\n{\n const members = objectWithKeys(value, ['message', 'sequence'], []);\n\n return {\n message: text(members.get('message')),\n sequence: integer(members.get('sequence')),\n };\n}\n\nexport function decodeListItemsRequest(value: CanonicalValue): ListItemsRequest\n{\n const members = objectWithKeys(value, ['limit'], ['cursor']);\n const request: ListItemsRequest = { limit: integer(members.get('limit')) };\n if (members.has('cursor'))\n {\n request.cursor = text(members.get('cursor'));\n }\n\n return request;\n}\n\nfunction objectWithKeys(\n value: CanonicalValue,\n required: string[],\n optional: string[],\n): CanonicalObject\n{\n if (!(value instanceof Map))\n {\n throw new ContractTypeError();\n }\n for (const key of required)\n {\n if (!value.has(key))\n {\n throw new ContractTypeError();\n }\n }\n for (const key of value.keys())\n {\n if (!required.includes(key) && !optional.includes(key))\n {\n throw new ContractTypeError();\n }\n }\n\n return value;\n}\n\nfunction text(value: CanonicalValue | undefined): string\n{\n if (typeof value !== 'string')\n {\n throw new ContractTypeError();\n }\n\n return value;\n}\n\nfunction integer(value: CanonicalValue | undefined): bigint\n{\n if (typeof value !== 'bigint')\n {\n throw new ContractTypeError();\n }\n\n return value;\n}\n\n// ============================================================================\n// Encoding\n// ============================================================================\n\nexport function encodeHandshakeResponse(sessionId: string, expiresAtMillis: bigint): CanonicalValue\n{\n return new Map<string, CanonicalValue>([\n ['sessionId', sessionId],\n ['expiresAtMillis', expiresAtMillis],\n ]);\n}\n\nexport function encodeEchoResponse(message: string, sequence: bigint, serverTimeMillis: bigint): CanonicalValue\n{\n return new Map<string, CanonicalValue>([\n ['message', message],\n ['sequence', sequence],\n ['serverTimeMillis', serverTimeMillis],\n ]);\n}\n\nexport function encodeListItemsResponse(items: ContractItem[], nextCursor: string | null): CanonicalValue\n{\n const encodedItems: CanonicalValue = items.map((item) => new Map<string, CanonicalValue>([\n ['id', item.id],\n ['name', item.name],\n ['updatedAtMillis', item.updatedAtMillis],\n ]));\n const members = new Map<string, CanonicalValue>([['items', encodedItems]]);\n if (nextCursor !== null)\n {\n members.set('nextCursor', nextCursor);\n }\n\n return members;\n}\n","/**\n * The dev server's test hooks, mirroring the spfn-mobile reference server's\n * `/control` surface route for route so the mobile integration suites can\n * drive either server with only a URL change.\n *\n * `/control` is NOT part of the contract: nothing under it appears in the\n * bundle, no SDK knows it exists, and its answers are plain objects rather\n * than contract envelopes. Every route except the readiness probe requires\n * the per-launch token; the token is never logged.\n *\n * @module server/client-proof/dev-control\n */\nimport { encodeCanonicalJson, parseCanonicalJson, type CanonicalValue } from './canonical-json';\nimport { ClientProofState, TestClock } from './state';\n\nexport const CONTROL_PREFIX = '/control/';\n\nexport const CONTROL_TOKEN_HEADER = 'x-spfn-reference-control';\n\nconst HTTP_OK = 200;\nconst HTTP_BAD_REQUEST = 400;\nconst HTTP_FORBIDDEN = 403;\nconst HTTP_NOT_FOUND = 404;\nconst HTTP_CONFLICT = 409;\n\nconst MAX_CONTROL_BODY_BYTES = 4096;\n\nexport async function handleControlRequest(\n state: ClientProofState,\n controlToken: string,\n path: string,\n request: Request,\n): Promise<Response>\n{\n if (path === '/control/health')\n {\n return answer(HTTP_OK, new Map<string, CanonicalValue>([['status', 'ok']]));\n }\n if (request.headers.get(CONTROL_TOKEN_HEADER) !== controlToken)\n {\n return answer(HTTP_FORBIDDEN, failure('control token'));\n }\n\n const raw = new Uint8Array(await request.arrayBuffer());\n const body = raw.length > MAX_CONTROL_BODY_BYTES ? raw.slice(0, MAX_CONTROL_BODY_BYTES) : raw;\n\n switch (path)\n {\n case '/control/stats':\n return stats(state);\n case '/control/reset':\n state.reset();\n\n return ok();\n case '/control/expire-sessions':\n state.expireSessions();\n\n return ok();\n case '/control/register-key':\n return registerKey(state, body);\n case '/control/revoke-key':\n return revokeKey(state, body);\n case '/control/session-ttl':\n return sessionTtl(state, body);\n case '/control/hold':\n return hold(state, body);\n case '/control/advance-clock':\n return advanceClock(state, body);\n default:\n return answer(HTTP_NOT_FOUND, failure('unknown control route'));\n }\n}\n\n// ---- routes ----------------------------------------------------------------\n\nfunction stats(state: ClientProofState): Response\n{\n const counters = state.stats();\n\n return answer(HTTP_OK, withOk(new Map<string, CanonicalValue>([\n ['echoCount', BigInt(counters.echoCount)],\n ['handshakeCount', BigInt(counters.handshakeCount)],\n ['itemsListCount', BigInt(counters.itemsListCount)],\n ['liveSessionCount', BigInt(counters.liveSessionCount)],\n ['refusalCount', BigInt(counters.refusalCount)],\n ['requestCount', BigInt(counters.requestCount)],\n ['spentNonceCount', BigInt(counters.spentNonceCount)],\n ])));\n}\n\n/**\n * Registers the public key a test client generated — the asymmetric\n * counterpart of the shared-key provisioning the HMAC profile injected at\n * construction. The body carries only the public half (SPKI DER base64); no\n * secret ever crosses this route.\n */\nfunction registerKey(state: ClientProofState, body: Uint8Array): Response\n{\n const keyId = stringField(body, 'keyId');\n const publicKey = stringField(body, 'publicKey');\n if (keyId === null)\n {\n return badRequest('keyId');\n }\n if (publicKey === null)\n {\n return badRequest('publicKey');\n }\n try\n {\n state.registerPublicKey(keyId, publicKey);\n }\n catch\n {\n return badRequest('publicKey');\n }\n\n return ok();\n}\n\nfunction revokeKey(state: ClientProofState, body: Uint8Array): Response\n{\n const keyId = stringField(body, 'keyId');\n if (keyId === null)\n {\n return badRequest('keyId');\n }\n state.revokeKey(keyId);\n\n return ok();\n}\n\nfunction sessionTtl(state: ClientProofState, body: Uint8Array): Response\n{\n const ttlMillis = integerField(body, 'ttlMillis');\n if (ttlMillis === null)\n {\n return badRequest('ttlMillis');\n }\n state.setSessionTtlMillis(Number(ttlMillis));\n\n return ok();\n}\n\nfunction hold(state: ClientProofState, body: Uint8Array): Response\n{\n const path = stringField(body, 'path');\n const millis = integerField(body, 'millis');\n const count = integerField(body, 'count');\n if (path === null)\n {\n return badRequest('path');\n }\n if (millis === null)\n {\n return badRequest('millis');\n }\n if (count === null)\n {\n return badRequest('count');\n }\n state.holdPath(path, Number(millis), Number(count));\n\n return ok();\n}\n\n/**\n * Moves a test clock forward. Refused when the server runs on the wall clock,\n * because silently doing nothing is how a test passes for the wrong reason.\n */\nfunction advanceClock(state: ClientProofState, body: Uint8Array): Response\n{\n const clock = state.clockRef;\n if (!(clock instanceof TestClock))\n {\n return answer(HTTP_CONFLICT, failure('server is running on the system clock'));\n }\n const millis = integerField(body, 'millis');\n if (millis === null)\n {\n return badRequest('millis');\n }\n clock.advance(Number(millis));\n\n return ok();\n}\n\n// ---- plumbing --------------------------------------------------------------\n\nfunction members(body: Uint8Array): Map<string, CanonicalValue> | null\n{\n if (body.length === 0)\n {\n return new Map();\n }\n let parsed: CanonicalValue;\n try\n {\n parsed = parseCanonicalJson(body);\n }\n catch\n {\n return null;\n }\n\n return parsed instanceof Map ? parsed : null;\n}\n\nfunction stringField(body: Uint8Array, field: string): string | null\n{\n const value = members(body)?.get(field);\n\n return typeof value === 'string' ? value : null;\n}\n\nfunction integerField(body: Uint8Array, field: string): bigint | null\n{\n const value = members(body)?.get(field);\n\n return typeof value === 'bigint' ? value : null;\n}\n\nfunction badRequest(field: string): Response\n{\n return answer(HTTP_BAD_REQUEST, failure(`missing or malformed field: ${field}`));\n}\n\nfunction ok(): Response\n{\n return answer(HTTP_OK, withOk(new Map()));\n}\n\nfunction failure(reason: string): Map<string, CanonicalValue>\n{\n return new Map<string, CanonicalValue>([['ok', false], ['reason', reason]]);\n}\n\nfunction withOk(extra: Map<string, CanonicalValue>): Map<string, CanonicalValue>\n{\n extra.set('ok', true);\n\n return extra;\n}\n\nfunction answer(status: number, value: Map<string, CanonicalValue>): Response\n{\n const bytes = encodeCanonicalJson(value);\n const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n\n return new Response(buffer, { status, headers: { 'content-type': 'application/json' } });\n}\n","/**\n * The mobile contract bundle — what spfn-mobile's Swift/Kotlin codegen reads.\n *\n * SPFN primitives owns the contract; this module assembles the bundle so the\n * export is produced here rather than transcribed in the consumer. Two kinds of\n * value go into it:\n *\n * - **Derived.** Operations, wire headers, proof-input fields, replay window and\n * HTTP statuses are read from the modules that implement them. Changing the\n * server changes the export.\n * - **Declared.** Type shapes, error summaries and the prose that describes the\n * canonicalization and admission rules are written here. They are not derived\n * from anything: no runtime value carries them. `contract-export.test.ts`\n * runs the real decoders and encoders against every declaration, so a\n * declaration that stops describing the server fails the suite.\n *\n * @module server/client-proof/contract-bundle\n */\nimport { createHash } from 'node:crypto';\n\nimport { KEY_TTL_DAYS } from '../lib/key-policy';\nimport { KEY_ALGORITHM } from '../types';\nimport { CLIENT_PROOF_CONTENT_TYPE, CLIENT_PROOF_HEADERS } from './admission';\nimport { AUTH_SURFACE_OPERATIONS, CONTRACT_OPERATIONS } from './contract-types';\nimport {\n CLIENT_PROOF_PROFILE,\n DEFAULT_REPLAY_WINDOW_MILLIS,\n PROOF_INPUT_FIELDS,\n PROOF_INPUT_SEPARATOR,\n} from './proof';\nimport { CLIENT_PROOF_ERROR_CODES, HTTP_STATUS } from './refusal';\nimport { CLIENT_IDENTITY_HEADERS, CLIENT_KINDS, SERVER_CONTRACT_HEADERS } from './wire-headers';\n\n/**\n * The version this export publishes. A mistake becomes a new version.\n *\n * The line is 0.x on purpose. The contract has one consumer, it is still\n * alpha, and its first export shipped a type spelling the consumer could not\n * parse — a surface that green has not earned a stable major. Under 0.x a\n * breaking change is a minor bump, which is what that correction actually was;\n * publishing it as 1.0.1 called a breaking change a patch.\n *\n * 1.0.0 and 1.0.1 existed briefly and are withdrawn. Neither was consumed.\n *\n * 0.2.0 revises the proof mechanism from HMAC-SHA-256 (a shared key) to ECDSA\n * P-256 (a registered public key) — breaking, hence a minor bump, taken while\n * the consumer count is zero. The proof-input, wire headers, admission order\n * and error codes are unchanged.\n *\n * 0.3.0 exports the existing `/_auth` enrollment surface (register, login,\n * native OAuth, key rotation) as contract operations, introduces the unproven\n * operation class (`authProfile: 'none'`), the `boolean` scalar the enrollment\n * responses need, and the key-TTL metadata. A surface addition under 0.x is a\n * minor bump. The clientProofV1 profile itself is unchanged from 0.2.0.\n *\n * 0.3.1 adds the optional `accessToken` field to `OauthNativeRequest`, which\n * Kakao needs to resolve an email claim its id_token omits. A patch, not a\n * minor: nothing existing changes meaning, a generated consumer that never\n * sends the field still matches the server, and the supported range is\n * unchanged — so a consumer pinned at 0.3.0 stays inside it rather than\n * falling out of a range it is in fact still compatible with.\n *\n * 0.4.0 binds `OauthNativeRequest.nonce` to the key being enrolled: it must be\n * the `fingerprint` of the submitted `publicKey`. The field list is untouched,\n * but a consumer that mints a random nonce is now refused, so this is breaking\n * and the range moves with it. Without the binding a valid id_token is enough\n * to enroll any key — the token is bearer-shaped and travels, while the web\n * OAuth flow keeps its key inside CSRF-bound encrypted state.\n *\n * 0.4.1 adds the key-management operations (list, revoke, revoke-all) and the\n * types they carry. A patch: existing operations and types are untouched, and a\n * consumer generated against 0.4.0 keeps matching the server, so the supported\n * range does not move. All three are POST with their arguments in the body —\n * the proof signs body bytes, which have a canonicalization rule, while a value\n * in the path does not.\n *\n * 0.4.2 gives the REST surface a readable failure: every error response now\n * carries the `{\"error\":{\"code\",\"message\",\"requestId\"}}` envelope next to the\n * web fields, and `auth.enroll.oauthNative`'s twelve refusals are listed as\n * codes with their status and retryability. A patch: no request or response\n * type moves, and a consumer generated against 0.4.1 could not read these\n * failures at all — it saw one undecodable body whatever went wrong — so\n * nothing it relies on changes and the supported range stays put.\n *\n * 0.5.0 widens the type grammar so an app contract can describe shapes the auth\n * surface never needed — a floating-point `number`, `map<string,T>`, and a named\n * enum whose declaration carries values instead of fields — and states the date\n * convention rather than adding a date scalar: a moment is an integer of\n * milliseconds since the Unix epoch in a field whose name ends `AtMillis`.\n *\n * Widening the grammar alone would have been a patch. What makes this breaking is\n * that `KeySummary` did not follow the convention: `createdAt`, `lastUsedAt`,\n * `expiresAt` and `revokedAt` were ISO 8601 strings, so the same contract stated\n * one representation and shipped two. They are now `createdAtMillis`,\n * `lastUsedAtMillis`, `expiresAtMillis` and `revokedAtMillis` integers, and\n * `listKeys` returns milliseconds.\n *\n * Taken now because the cost only grows: no generated consumer reads these types\n * yet — spfn-mobile's codegen path is unbuilt — and an exception documented\n * instead of removed would have kept Swift's `ISO8601DateFormatter` rejecting\n * fractional seconds as a live way for the two SDKs to disagree, on exactly\n * these four fields. An app reading `createdAt` from `authApi.listKeys()` must\n * move to `createdAtMillis`.\n *\n * 0.6.0 puts the contract version on the wire. A client states its kind, its own\n * release and the contract version it was generated from; the server answers on\n * every response with the version it serves and the range it accepts. A client\n * that ships separately from the server and states no contract version is\n * refused — until now the disagreement surfaced as an undecodable body, which\n * told the user nothing. None of it enters the proof input.\n *\n * `algorithm` becomes the `KeyAlgorithm` enum in the three requests that carry it\n * and in `KeySummary`. The routes have always constrained it to those values while\n * this contract said `string`, so the contract understated the server; it is\n * breaking because it changes what codegen produces for an existing field.\n *\n * The grammar also stops telling a consumer what to do with a value outside a\n * declared set. That was an instruction to the decoder, and a contract states\n * what the server does — how to survive a list that grows is the client's\n * decision to make. No list here is promised to be closed: an algorithm can be\n * withdrawn for a weakness found after this was written, and a contract that\n * promised otherwise would be promising something it cannot keep.\n *\n * 0.6.1 records when each operation became available: every operation now carries\n * `since`, the contract version it first appeared in, backfilled from this\n * repository's own history, and the optional `deprecatedIn` / `removedIn` that a\n * later version will fill in. Nothing is deprecated today, so both are absent\n * everywhere.\n *\n * A patch: no request or response type moves, no operation is added or taken\n * away, and this contract's policy is `allOrNothing`, so the new fields change no\n * verdict — a client is still admitted or refused by one version for the whole\n * surface. They are here so a deprecation has somewhere to be recorded when the\n * first one happens, and so an app contract, which decides per operation, reads\n * availability in the same shape rather than inventing a second one.\n *\n * 0.7.0 removes the `number` scalar and gives the grammar `decimal<scale>`: the\n * wire value is an integer and what it means is that integer divided by 10 to the\n * scale, so `decimal<2>` carries 1999 for 19.99. Canonical JSON does not move —\n * it already admits signed 64-bit integers only and calls a fraction an error,\n * which is what a `number` field would have had to be written as. The grammar and\n * the encoding had been stating different things, and only the encoding ran.\n *\n * Two rules ride the spelling. The scale is part of the type, so changing it is\n * breaking and takes a version bump, and the field is renamed to carry its new\n * unit rather than be quietly remeasured under the old name — the same reasoning\n * that put `AtMillis` in the name of every moment here. And a generator emits a\n * decimal type — Swift `Decimal`, Kotlin `BigDecimal` — never a binary float, and\n * rejects a value finer than the declared scale at encoding time instead of\n * rounding it, because rounding lets the client decide what a value the server\n * declared exactly is worth.\n *\n * Breaking because a declared scalar is gone. A consumer generated against 0.6.x\n * that meets `decimal<2>` fails at generation time, which is what this grammar's\n * own rule asks for — an unknown spelling is a contract error, not something to\n * guess at. Nothing deployed breaks: no type in this contract used `number`, so\n * the removal has zero usages, and it is taken now because the alternative is\n * carrying a scalar the encoding refuses until something depends on it.\n *\n * 0.8.0 applies to the error envelope the rule 0.6.0 applied to the grammar.\n * `unknownCodePolicy: 'reject'` and the rule beside it told a decoder what to do\n * with a code this bundle does not list, and `additionalFields` told it to ignore\n * the extra top-level fields rather than reject them. The test is whether the\n * server would notice a client doing the opposite, and it would not. Both are\n * replaced by the fact behind them: the server sends codes outside the list, and\n * the body carries fields beside the error object. Breaking, because removing a\n * declaration changes what a generated consumer is built from.\n */\nexport const CONTRACT_VERSION = '0.8.0';\nexport const CONTRACT_MAJOR = 0;\nexport const CONTRACT_NAME = 'spfn-mobile-contract';\n\n/**\n * Under 0.x the minor carries breaking changes, so the range stops at 0.9.0.\n *\n * 0.8.0 moves the floor with it, for the same reason 0.7.0 did. A consumer\n * generated against 0.7.x was generated from declarations this bundle no longer\n * carries, so it is refused CONTRACT_UNSUPPORTED rather than left reading a field\n * that is gone. No such consumer is deployed.\n */\nexport const CONTRACT_SUPPORTED_RANGE = '>=0.8.0 <0.9.0';\n\n/** What spfn-mobile's validator expects an upstream-exported bundle to name. */\nexport const EXPORT_ORIGIN = 'spfn-primitives-ci-export';\n\n/**\n * Bumped whenever the assembled shape changes, independent of the contract.\n *\n * The bump follows what a reader of this shape can still find. A major when a\n * section or key is removed or renamed, so code reading it stops finding what it\n * read; a minor when the shape only grows. 5.0.0 is a major because `typeGrammar`\n * lost `integerVersusNumber`, where 4.1.0 was a minor for availability fields that\n * were purely added.\n */\nexport const EXPORTER_VERSION = '@spfn/auth/contract-bundle@5.0.0';\n\n/**\n * The scalars the grammar admits.\n *\n * There is no floating-point scalar. A fractional value is `decimal<scale>`, an\n * integer on the wire with its scale declared in the type, because canonical JSON\n * carries signed 64-bit integers only and treats a fraction as an error — a\n * floating-point scalar was a shape the encoding would have refused. `integer`\n * stays separate from it so a count is never given a scale it does not have.\n *\n * There is no date scalar. A moment is an integer of milliseconds since the Unix\n * epoch in a field whose name ends `AtMillis`, which is what every existing type\n * already does.\n */\ntype ScalarTypeName = 'string' | 'integer' | 'boolean';\n\n/**\n * The scales `decimal<scale>` admits.\n *\n * Scale 0 is `integer` written the long way, so it is not a scale. The ceiling is\n * 18 because 10^18 is the largest power of ten a signed 64-bit integer holds, and\n * the wire value is such an integer — above 18 there is no integer part left to\n * carry.\n *\n * Spelled as a union rather than checked at runtime so an out-of-range scale\n * fails to compile here, where the declaration is written, rather than reaching a\n * consumer's generator as a type it cannot parse.\n */\ntype DecimalScale =\n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9\n | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18;\n\n/**\n * A fixed-point value: the integer on the wire divided by 10 to the scale.\n *\n * Parameterized like `array<T>`, and read by the same parser — a consumer that\n * does not recognise the prefix reads `decimal<2>` as a type named \"decimal<2>\"\n * and fails at compile time, which is the grammar's rule for an unknown spelling.\n */\ntype DecimalTypeName = `decimal<${DecimalScale}>`;\n\n/**\n * Declared names a field is allowed to reference — the types in\n * `CONTRACT_TYPES` and the enums in `CONTRACT_ENUMS` that are actually used.\n *\n * Hand-listed rather than derived: the declarations below are what would define\n * it, so deriving it would be circular, and a misspelled name has to fail here\n * rather than reach the consumer as a type it cannot find.\n */\ntype ReferencedTypeName = 'Item' | 'KeySummary' | 'KeyAlgorithm';\n\ntype ElementTypeName = ScalarTypeName | DecimalTypeName | ReferencedTypeName;\n\n/**\n * The field-type grammar the consumer's codegen parses.\n *\n * `array<T>` and `map<string,T>` are the only container spellings: spfn-mobile's\n * `FieldType.parse` reads a recognised container prefix as a container and\n * everything else as a named type, so `Item[]` would silently become a type\n * named \"Item[]\" and fail at compile time rather than at parse time.\n *\n * A map's key is always a string because JSON has no other key type. Spelling it\n * out anyway keeps the consumer from having to assume it.\n *\n * This union is narrower than the grammar it guards: the grammar lets a\n * container hold another container, and the consumer's parser recurses, while\n * here a container holds one element type. Narrower is the safe direction —\n * nothing invalid can be declared — and it widens when a nested container is\n * first needed.\n */\ntype FieldTypeName =\n | ElementTypeName\n | `array<${ElementTypeName}>`\n | `map<string,${ElementTypeName}>`;\n\ninterface FieldDeclaration\n{\n name: string;\n type: FieldTypeName;\n optional: boolean;\n}\n\ninterface TypeDeclaration\n{\n name: string;\n fields: FieldDeclaration[];\n}\n\n/**\n * A named set of string values, declared by name so a field can reference it the\n * same way it references an object type.\n *\n * The values are the ones the server accepts and sends **now**. A set is not\n * promised to stay as it is: an algorithm can be added, and one can be withdrawn\n * for a weakness found after this was written. What a consumer does when it meets\n * a value it does not know is the consumer's decision — a generated client that\n * cannot survive a grown list is a defect in the generator, not something this\n * contract can prevent by declaring the set closed.\n */\ninterface EnumDeclaration\n{\n name: string;\n values: readonly string[];\n}\n\nfunction required(name: string, type: FieldDeclaration['type']): FieldDeclaration\n{\n return { name, type, optional: false };\n}\n\nfunction optional(name: string, type: FieldDeclaration['type']): FieldDeclaration\n{\n return { name, type, optional: true };\n}\n\n/**\n * The contract types.\n *\n * The clientProofV1 request types mirror the decoders in `contract-types.ts`\n * and the response types mirror the encoders. Neither reads this table — the\n * conformance vectors are what hold the two in agreement.\n *\n * The `/_auth` surface types mirror the TypeBox route schemas (input body +\n * Next.js interceptor body merged, since a mobile client sends the whole\n * body itself) and the service result interfaces. The optional free-form\n * extension fields (`metadata`, `profile`) are deliberately not declared:\n * they are outside this grammar, the server tolerates their absence, and a\n * consumer generated from this contract never needs to send them.\n */\nexport const CONTRACT_TYPES: readonly TypeDeclaration[] = [\n {\n name: 'HandshakeRequest',\n fields: [\n required('clientId', 'string'),\n required('keyId', 'string'),\n required('nonce', 'string'),\n required('issuedAtMillis', 'integer'),\n ],\n },\n {\n name: 'HandshakeResponse',\n fields: [\n required('sessionId', 'string'),\n required('expiresAtMillis', 'integer'),\n ],\n },\n {\n name: 'EchoRequest',\n fields: [\n required('message', 'string'),\n required('sequence', 'integer'),\n ],\n },\n {\n name: 'EchoResponse',\n fields: [\n required('message', 'string'),\n required('sequence', 'integer'),\n required('serverTimeMillis', 'integer'),\n ],\n },\n {\n name: 'ListItemsRequest',\n fields: [\n required('limit', 'integer'),\n optional('cursor', 'string'),\n ],\n },\n {\n name: 'Item',\n fields: [\n required('id', 'string'),\n required('name', 'string'),\n required('updatedAtMillis', 'integer'),\n ],\n },\n {\n name: 'ListItemsResponse',\n fields: [\n required('items', 'array<Item>'),\n optional('nextCursor', 'string'),\n ],\n },\n {\n name: 'RegisterRequest',\n fields: [\n optional('email', 'string'),\n optional('phone', 'string'),\n required('verificationToken', 'string'),\n required('password', 'string'),\n required('publicKey', 'string'),\n required('keyId', 'string'),\n required('fingerprint', 'string'),\n required('algorithm', 'KeyAlgorithm'),\n ],\n },\n {\n name: 'RegisterResponse',\n fields: [\n required('userId', 'string'),\n required('publicId', 'string'),\n optional('email', 'string'),\n optional('phone', 'string'),\n ],\n },\n {\n name: 'LoginRequest',\n fields: [\n optional('email', 'string'),\n optional('phone', 'string'),\n required('password', 'string'),\n required('publicKey', 'string'),\n required('keyId', 'string'),\n required('fingerprint', 'string'),\n required('algorithm', 'KeyAlgorithm'),\n optional('oldKeyId', 'string'),\n ],\n },\n {\n name: 'LoginResponse',\n fields: [\n required('userId', 'string'),\n required('publicId', 'string'),\n optional('email', 'string'),\n optional('phone', 'string'),\n required('passwordChangeRequired', 'boolean'),\n ],\n },\n {\n name: 'OauthNativeRequest',\n fields: [\n required('idToken', 'string'),\n required('nonce', 'string'),\n optional('accessToken', 'string'),\n required('publicKey', 'string'),\n required('keyId', 'string'),\n required('fingerprint', 'string'),\n required('algorithm', 'KeyAlgorithm'),\n ],\n },\n {\n name: 'OauthNativeResponse',\n fields: [\n required('userId', 'string'),\n required('keyId', 'string'),\n required('isNewUser', 'boolean'),\n ],\n },\n {\n name: 'RotateKeyRequest',\n fields: [\n required('publicKey', 'string'),\n required('keyId', 'string'),\n required('fingerprint', 'string'),\n required('algorithm', 'KeyAlgorithm'),\n ],\n },\n {\n name: 'RotateKeyResponse',\n fields: [\n required('success', 'boolean'),\n required('keyId', 'string'),\n ],\n },\n {\n name: 'ListKeysRequest',\n fields: [\n optional('includeRevoked', 'boolean'),\n ],\n },\n {\n name: 'KeySummary',\n fields: [\n required('keyId', 'string'),\n optional('deviceName', 'string'),\n optional('platform', 'string'),\n required('algorithm', 'KeyAlgorithm'),\n required('fingerprintPrefix', 'string'),\n required('createdAtMillis', 'integer'),\n optional('lastUsedAtMillis', 'integer'),\n optional('expiresAtMillis', 'integer'),\n required('isExpired', 'boolean'),\n required('isActive', 'boolean'),\n optional('revokedAtMillis', 'integer'),\n ],\n },\n {\n name: 'ListKeysResponse',\n fields: [\n required('keys', 'array<KeySummary>'),\n ],\n },\n {\n name: 'RevokeKeyRequest',\n fields: [\n required('keyId', 'string'),\n ],\n },\n {\n name: 'RevokeKeyResponse',\n fields: [\n required('keyId', 'string'),\n required('selfRevoked', 'boolean'),\n ],\n },\n {\n name: 'RevokeAllKeysRequest',\n fields: [\n optional('includeCurrent', 'boolean'),\n ],\n },\n {\n name: 'RevokeAllKeysResponse',\n fields: [\n required('revokedCount', 'integer'),\n required('currentKeyRevoked', 'boolean'),\n ],\n },\n];\n\n/**\n * The enums this contract declares.\n *\n * `KeyAlgorithm` is read from the server's own list rather than transcribed, so\n * an algorithm added or withdrawn there moves this declaration with it.\n */\nexport const CONTRACT_ENUMS: readonly EnumDeclaration[] = [\n { name: 'KeyAlgorithm', values: [...KEY_ALGORITHM] },\n];\n\n/** One line per code describing what it means on the wire. */\nconst ERROR_SUMMARIES: Record<string, string> = {\n PROOF_INVALID: 'the client proof did not verify',\n PROOF_REPLAYED: 'the nonce was already used inside the replay window',\n PROOF_EXPIRED: 'issuedAtMillis falls outside the replay window',\n SESSION_REVOKED: 'the key or session was revoked',\n PROFILE_REJECTED: 'an auth profile outside the allowlist was named',\n CONTRACT_UNSUPPORTED: 'the request is not the shape this contract describes',\n};\n\n/**\n * No refusal is retryable without changing the request.\n *\n * An auth-family code clears after a fresh handshake, which is a different\n * request, so replaying the same bytes never helps.\n */\nconst RETRYABLE = false;\n\ninterface RestSurfaceError\n{\n code: string;\n httpStatus: number;\n retryable: boolean;\n summary: string;\n}\n\n/**\n * Every way `auth.enroll.oauthNative` refuses, as codes a consumer can switch on.\n *\n * \"Every way\" includes the app's own `beforeRegister` check: what that check\n * decides is the app's business, but the response it produces is the\n * framework's — a fixed class name at a fixed status. Leaving it out would hand\n * every app that uses the hook an undecodable refusal.\n *\n * The codes are the server's own error class names rather than a second\n * vocabulary invented for mobile: two vocabularies would have to be kept in\n * step, and the mapping between them is exactly the place a wrong answer\n * hides.\n *\n * Only this operation's codes are listed. The `error` envelope now reaches\n * every REST operation, but a code list is a promise, and a promise about\n * routes whose failure paths have not been enumerated one by one would be a\n * guess. That the server sends codes outside this list is stated as\n * `unlistedCodes`; what a decoder does when it meets one is the decoder's\n * decision.\n */\nconst REST_SURFACE_ERRORS: readonly RestSurfaceError[] = [\n {\n code: 'ValidationError',\n httpStatus: 400,\n retryable: false,\n summary: 'the request body is not the shape the operation declares',\n },\n {\n code: 'NativeSignInUnsupportedError',\n httpStatus: 400,\n retryable: false,\n summary: 'this provider has no native id_token sign-in — a server configuration fact, not a user error',\n },\n {\n code: 'NonceKeyBindingError',\n httpStatus: 400,\n retryable: false,\n summary: 'the nonce is not the fingerprint of the submitted public key',\n },\n {\n code: 'InvalidKeyFingerprintError',\n httpStatus: 400,\n retryable: false,\n summary: 'the fingerprint is not the hash of the submitted public key',\n },\n {\n code: 'UnverifiedEmailLinkError',\n httpStatus: 400,\n retryable: false,\n summary: 'that email already has an account and the provider never verified it, so linking is refused',\n },\n {\n code: 'InvalidSocialTokenError',\n httpStatus: 401,\n retryable: false,\n summary: 'the id_token failed signature, issuer, audience, expiry, nonce or subject verification',\n },\n {\n code: 'AccountDisabledError',\n httpStatus: 403,\n retryable: false,\n summary: 'the account cannot open a session in its current status',\n },\n {\n code: 'AccountPendingDeletionError',\n httpStatus: 403,\n retryable: false,\n summary: 'the account is scheduled for deletion and must be restored before it can sign in',\n },\n {\n code: 'RegistrationRejectedError',\n httpStatus: 403,\n retryable: false,\n summary:\n 'the app refused this sign-up in its own beforeRegister check — reached only when the identity would '\n + 'create a new account, never when it links to an existing one',\n },\n {\n code: 'KeyIdAlreadyRegisteredError',\n httpStatus: 409,\n retryable: false,\n summary: 'that keyId is taken or was revoked — generate a fresh keyId and retry',\n },\n {\n code: 'TooManyRequestsError',\n httpStatus: 429,\n retryable: true,\n summary: 'the rate limit for this endpoint was exceeded; the same request succeeds after the window',\n },\n {\n code: 'Error',\n httpStatus: 500,\n retryable: false,\n summary: 'the server failed for a reason it does not describe to the client',\n },\n];\n\nexport interface MobileContractBundle\n{\n [key: string]: unknown;\n}\n\n/** Assembles the bundle. Pure — same inputs, same object, every time. */\nexport function buildMobileContractBundle(): MobileContractBundle\n{\n return {\n bundleKind: 'UPSTREAM_EXPORT',\n origin: EXPORT_ORIGIN,\n originStatement:\n 'Generated from the route and contract definitions in SPFN primitives '\n + '(packages/auth/src/server/client-proof) and published from that repository. '\n + 'This file is generated output: edit the source modules and re-export, never this file.',\n contractName: CONTRACT_NAME,\n contractMajor: CONTRACT_MAJOR,\n contractVersion: CONTRACT_VERSION,\n supportedRange: CONTRACT_SUPPORTED_RANGE,\n exporterVersion: EXPORTER_VERSION,\n authProfiles: {\n allowed: [CLIENT_PROOF_PROFILE],\n unknownProfilePolicy: 'reject',\n mixingWithinSession: 'prohibited',\n },\n operationAuthClasses: {\n none:\n 'the unproven class: the operation is accepted with neither proof headers nor a session header, '\n + 'because it is called before any key exists to sign with (enrollment and login)',\n [CLIENT_PROOF_PROFILE]:\n 'the operation is admitted by the clientProofV1 admission order; requiresSession states whether '\n + 'the session header travels',\n rule:\n 'an operation whose authProfile is not none refuses an unproven call exactly as it refuses any '\n + 'failed admission; nothing is downgraded to anonymous handling',\n },\n operationAvailability: {\n since:\n 'the contract version the operation first appeared in. Every operation carries one, and it is '\n + 'never rewritten: it is a fact about this contract\\'s history',\n deprecatedIn:\n 'the contract version that marked the operation deprecated, absent until one does. A deprecated '\n + 'operation is still served — the mark opens the grace period, it does not end the operation',\n removedIn:\n 'the contract version that removed the operation. A removed operation leaves this list, so no '\n + 'entry carries it today; it is the field a removal is recorded in when the first one happens',\n ordering:\n 'since <= deprecatedIn < removedIn, and removedIn never appears without deprecatedIn: an '\n + 'operation is marked in one version and taken away in a later one, never both at once',\n verdictRule:\n 'under this contract\\'s allOrNothing policy these three fields decide nothing. One contract '\n + 'version passes or refuses this whole surface, so availability here is description a reader '\n + 'and a changelog use, not an input the server compares against. A contract whose policy is '\n + 'perOperation reads the same fields as a verdict input',\n procedure:\n 'a removal is mark then wait then remove: deprecatedIn in one version, the operation still '\n + 'served, removedIn in a later one. Nothing is removed in the version that first deprecates it',\n },\n keyPolicy: {\n ttlDays: KEY_TTL_DAYS,\n rotationOperation: 'auth.keys.rotate',\n rule:\n 'a registered public key expires ttlDays after registration; an expired or revoked key is refused '\n + 'at the revocation step (SESSION_REVOKED, non-disclosing), so the client rotates its key via the '\n + 'rotation operation before the TTL runs out',\n },\n nativeEnrollment: {\n appliesTo: 'auth.oauth.native',\n nonceRule:\n 'the nonce sent with a native id_token must be the fingerprint field of the same request, which '\n + 'is the SHA-256 of the DER bytes of publicKey in lowercase hex; the server refuses the call '\n + 'when the two differ or when the fingerprint is not that key\\'s hash',\n appleVariant:\n 'Apple hashes the nonce it receives, so the client puts sha256hex(fingerprint) in Apple\\'s '\n + 'authorization request while still sending the raw fingerprint as nonce; every other provider '\n + 'carries the raw value both ways',\n rationale:\n 'an id_token is bearer-shaped and travels, so verifying it alone lets whoever holds one enroll '\n + 'any key on that account; deriving the nonce from the key means a stolen id_token carries the '\n + 'victim\\'s fingerprint and cannot be paired with the attacker\\'s key',\n },\n restOperations: {\n appliesTo: 'every operation whose path starts with /_auth',\n requestBody:\n 'plain JSON of the request type, validated server-side; canonical-JSON encoding is required only '\n + 'when the call is proven (the proof binds the canonical bytes)',\n responseBody: 'the response type as plain JSON, with no envelope around it',\n errorEnvelope:\n 'the same {\"error\":{\"code\",\"message\",\"requestId\"}} envelope every operation uses, carried '\n + 'alongside the SPFN web fields (__type and the error class\\'s own public fields) in one body: '\n + 'the web client restores an error class from __type while a generated client reads error.code '\n + 'and ignores the rest. The codes are the server error class names listed under errors with '\n + 'surface \"rest\", not the six clientProofV1 refusal codes — those reach only proven calls',\n pathTemplate:\n 'a {name} segment is a path parameter the client substitutes before signing or sending; '\n + '{provider} is the social provider id (google, apple, kakao, naver)',\n policy:\n 'rate limits and other route policies are server posture, not contract surface: this bundle '\n + 'states wire shapes only',\n },\n canonicalJson: {\n algorithm: 'SPFN-CANON-JSON-1',\n objectKeyOrder: 'ascending by UTF-8 byte sequence',\n whitespace: 'none',\n numbers: 'signed 64-bit integers only; a fractional or non-finite number is a canonicalization error',\n stringEscapes:\n 'quotation mark and reverse solidus escaped; C0 controls use \\\\b \\\\f \\\\n \\\\r \\\\t where defined '\n + 'and \\\\u00XX otherwise; every other scalar is emitted literally',\n encoding: 'UTF-8',\n },\n clientProofV1: {\n profile: CLIENT_PROOF_PROFILE,\n proofInput: {\n algorithm: 'SPFN-PROOF-INPUT-1',\n separator: PROOF_INPUT_SEPARATOR,\n fields: [...PROOF_INPUT_FIELDS],\n fieldRules:\n 'no field value may contain a C0 control character; a value that does is a proof-input error, '\n + 'because the separator would otherwise be ambiguous',\n bodySha256:\n 'lowercase base16 SHA-256 of the canonical JSON request body; the literal string of 64 zero '\n + 'characters when an operation has no body',\n },\n digest: 'SHA-256',\n signature: {\n algorithm: 'ECDSA P-256 with SHA-256',\n encoding: 'raw r||s, two 32-byte big-endian integers, 64 bytes total, base16-lower (128 hex characters)',\n derRule:\n 'a DER-encoded signature is rejected on the wire; a platform signer that emits DER converts to '\n + 'raw r||s before sending',\n lowS:\n 'low-S normalization is not required; uniqueness is owned by the nonce and replay window, so '\n + 'signature malleability cannot replay a request',\n publicKey: 'SPKI DER, base64; x-spfn-key-id names a registered public key',\n },\n proofEncoding: 'base16-lower',\n replayWindowMillis: DEFAULT_REPLAY_WINDOW_MILLIS,\n clientIdRule:\n \"clientId identifies the key owner; the REST surface refuses a proof whose clientId is not the key's \"\n + 'owner id, with the same PROOF_INVALID a failed signature answers',\n replayRule:\n 'a (clientId, nonce) pair is accepted at most once inside the replay window; a repeat is PROOF_REPLAYED',\n revocationRule:\n 'a revoked keyId is rejected before the proof is verified; the outcome is SESSION_REVOKED and never '\n + 'PROOF_INVALID, so revocation is not inferable from a proof failure',\n admissionOrder: ['revocation', 'session', 'expiry', 'replay', 'proof'],\n nonceRule: 'a nonce is spent only when the request is admitted; a refused request leaves it unused',\n },\n wireMapping: {\n requestContentType: CLIENT_PROOF_CONTENT_TYPE,\n headers: { ...CLIENT_PROOF_HEADERS },\n headerOrder: Object.keys(CLIENT_PROOF_HEADERS),\n contentTypeRule:\n 'the content-type header is present exactly when the request carries a body, and the body is always '\n + 'the canonical JSON of the request type',\n sessionRule: `requiresSession operations carry ${CLIENT_PROOF_HEADERS.session}; the handshake never does`,\n clientIdentity: {\n headers: { ...CLIENT_IDENTITY_HEADERS },\n kinds: [...CLIENT_KINDS],\n appliesTo:\n 'every operation, proven or not — enrollment and login are where a stale client is met first, '\n + 'and they carry no proof',\n kindRule:\n 'ios and android ship independently of the server and state the contract version they were '\n + 'generated from; web does not, because a browser bundle is deployed with the server that '\n + 'serves it and has no second version to reconcile',\n versionRule:\n 'the client version is the client\\'s own release — a store version for an app, a build for a '\n + 'browser bundle. It is unauthenticated and nothing is authorized by it',\n refusalRule:\n 'an ios or android client that states no contract version, or one outside the range in the '\n + 'response headers, is refused CONTRACT_UNSUPPORTED; a request naming no kind is not a '\n + 'deployed client and passes',\n proofRule:\n 'none of these headers enters the proof input: they are diagnostic, and PROOF_INPUT_FIELDS is '\n + 'unchanged from 0.5.0',\n },\n serverAnnouncement: {\n headers: { ...SERVER_CONTRACT_HEADERS },\n appliesTo: 'every response, including a refusal',\n rule:\n 'the server states the contract version it serves and the range it accepts. It states no more '\n + 'than that: comparing those against its own version and deciding what a user should be told '\n + 'is the client\\'s judgment, made in the client',\n },\n },\n compatibilityPolicy: {\n policy: 'allOrNothing',\n rule:\n 'one contract version is this whole surface\\'s pass or refusal. Partial compatibility in an auth '\n + 'primitive would mean admitting a client that agrees about some of the admission sequence and '\n + 'not the rest',\n contrast:\n 'an app contract generated from SPFN routes uses perOperation instead, where availability is '\n + 'recorded per operation and the verdict narrows to the operations a client actually calls. The '\n + 'two share this bundle format, so the policy is stated rather than inferred',\n availability:\n 'the since, deprecatedIn and removedIn fields on each operation, described under '\n + 'operationAvailability, are recorded here as well. Under allOrNothing they are descriptive: '\n + 'they are history, not a verdict input. Recording them regardless is what lets a deprecation '\n + 'be announced at all, and is the same shape a perOperation contract decides from',\n },\n typeGrammar: {\n scalars: ['string', 'integer', 'boolean'],\n decimal:\n 'decimal<scale>, where scale is an integer from 1 to 18. The value on the wire is an integer and '\n + 'what it means is that integer divided by 10 to the scale, so decimal<2> carries 1999 for 19.99. '\n + 'There is no floating-point scalar: canonical JSON admits signed 64-bit integers only and treats '\n + 'a fraction as an error. Scale 0 is integer written the long way and is not a valid scale, and 18 '\n + 'is the ceiling because 10^18 is the largest power of ten a signed 64-bit integer holds — above '\n + 'it no integer part is left to carry. This is the only decimal spelling.',\n decimalScaleRule:\n 'the scale is part of the type. Changing it is a breaking change and takes a version bump, and the '\n + 'field is renamed to carry its new unit rather than be remeasured under the same name — the same '\n + 'reason every moment in this contract is named AtMillis. A consumer that kept reading the old '\n + 'name would otherwise decode the same field at a scale nobody told it had moved.',\n decimalGeneratorRule:\n 'a generator emits a decimal type — Swift Decimal, Kotlin BigDecimal — and never a binary float. A '\n + 'value finer than the declared scale is rejected at encoding time and never rounded: rounding '\n + 'would let the client decide what a value the server declared exactly is worth, and it would do '\n + 'so silently.',\n array: 'array<T>, where T is itself a field type. This is the only array spelling.',\n map:\n 'map<string,T>, where T is itself a field type. The key is always string because JSON has no other '\n + 'key type. This is the only map spelling.',\n named: 'any other value names one of the types or enums below',\n enumRule:\n 'a name listed in \"enums\" is a set of string values rather than an object: its declaration carries '\n + 'values instead of fields. The values are the ones the server accepts and sends now; no set is '\n + 'promised to stay as it is, since a value can be added and one can be withdrawn for a weakness '\n + 'found later. What a consumer does with a value outside the set is the consumer\\'s decision',\n dateConvention:\n 'there is no date type. A moment in time is an integer of milliseconds since the Unix epoch and its '\n + 'field name ends in AtMillis — issuedAtMillis, expiresAtMillis, createdAtMillis. A second '\n + 'representation would leave a consumer choosing between two spellings of the same value.',\n dateConventionExceptions: 'none — every moment in this contract is an AtMillis integer',\n rule:\n 'a field type outside this grammar is a contract error, not something to guess at: a consumer that '\n + 'does not recognise a container or decimal spelling reads it as a type name and fails at compile '\n + 'time',\n },\n types: CONTRACT_TYPES.map((type) => ({\n name: type.name,\n fields: type.fields.map((field) => ({ ...field })),\n })),\n enums: CONTRACT_ENUMS.map((declaration) => ({\n name: declaration.name,\n values: [...declaration.values],\n })),\n operations: [...CONTRACT_OPERATIONS, ...AUTH_SURFACE_OPERATIONS].map((operation) => ({ ...operation })),\n errorEnvelope: {\n shape: '{\"error\":{\"code\":<string>,\"message\":<string>,\"requestId\":<string>}}',\n additionalFields:\n 'the body carries further top-level fields beside the error object — __type, message, and the '\n + 'error class\\'s own public fields. Only error.code classifies the failure',\n unlistedCodes:\n 'the server sends codes this list does not carry. Only the operations enumerated here have had '\n + 'their failure paths listed one by one, and every code is a server error class name rather '\n + 'than a value minted for this contract',\n },\n errors: [\n ...CLIENT_PROOF_ERROR_CODES.map((code) => ({\n code,\n httpStatus: HTTP_STATUS[code],\n retryable: RETRYABLE,\n summary: ERROR_SUMMARIES[code],\n surface: 'clientProofV1',\n })),\n ...REST_SURFACE_ERRORS.map((error) => ({ ...error, surface: 'rest' })),\n ],\n notes: [\n 'This bundle contains no secret, no real key and no production endpoint. Paths are shapes, not deployed routes.',\n 'It is generated output. Edit packages/auth/src/server/client-proof and re-run the export; never edit this file.',\n 'The single authority for this contract is SPFN primitives.',\n ],\n };\n}\n\n/**\n * No major in the filename while the line is 0.x: under 0.x the minor is what\n * breaks, so `v0` would name nothing useful. The version lives in the bundle\n * and the pin is the digest.\n */\nexport const BUNDLE_FILENAME = 'spfn-mobile-contract.json';\nexport const PROVENANCE_FILENAME = 'upstream-provenance.json';\nexport const REPOSITORY = 'git.superfunction.xyz/superfunction/primitives';\nexport const BUNDLE_REPO_PATH = `contracts/mobile/${BUNDLE_FILENAME}`;\n\n/**\n * The evidence spfn-mobile's validator requires before a lock may claim an\n * upstream export.\n *\n * `source.commit` is absent by construction: a file cannot carry the SHA of the\n * commit that contains it. The exporter states everything else and the consumer\n * records which commit it read.\n */\nexport function buildExportProvenance(bundleSha256: string): Record<string, unknown>\n{\n return {\n evidenceVersion: 1,\n origin: EXPORT_ORIGIN,\n exportedByUpstreamCI: true,\n exporterVersion: EXPORTER_VERSION,\n statement:\n 'This contract bundle was generated from the route and contract definitions in SPFN primitives '\n + '(packages/auth/src/server/client-proof) by packages/auth/scripts/export-mobile-contract.ts. '\n + 'It was not transcribed from any consumer.',\n source: {\n repository: REPOSITORY,\n bundlePath: BUNDLE_REPO_PATH,\n commit: 'RECORDED_BY_CONSUMER',\n commitRule:\n 'The consumer sets its own lock source.commit to the exact primitives commit it read this '\n + 'bundle from. A file cannot carry its own commit SHA.',\n },\n contract: {\n name: CONTRACT_NAME,\n version: CONTRACT_VERSION,\n major: CONTRACT_MAJOR,\n supportedRange: CONTRACT_SUPPORTED_RANGE,\n bundleSha256,\n },\n verification: {\n digest: `shasum -a 256 ${BUNDLE_REPO_PATH}`,\n regenerate: 'pnpm --filter @spfn/auth export:mobile-contract',\n enforcedBy: [\n 'packages/auth/src/server/client-proof/__tests__/contract-export.test.ts',\n '.github/workflows/verify-mobile-contract.yml',\n ],\n rule:\n 'The committed bundle must be byte-identical to what the exporter produces. The test above '\n + 'regenerates and compares, so an edited bundle fails the suite rather than shipping.',\n },\n notes: [\n 'The bundle carries no secret, no real key and no production endpoint.',\n 'A published contract version and digest are never modified. A mistake becomes a new version.',\n ],\n };\n}\n\n/**\n * The bundle as the bytes that get committed and digested.\n *\n * A value short enough to fit on one line stays on one line: it keeps field\n * declarations and short lists readable, and it is what makes the emitted text\n * stable across runs. Everything else is indented two spaces.\n */\nexport function serializeMobileContractBundle(bundle: MobileContractBundle): string\n{\n return `${render(bundle, 0)}\\n`;\n}\n\n/** Both files of the export, and the digest the consumer pins. */\nexport function renderMobileContractExport(): { bundle: string; provenance: string; bundleSha256: string }\n{\n const bundle = serializeMobileContractBundle(buildMobileContractBundle());\n const bundleSha256 = createHash('sha256').update(bundle, 'utf8').digest('hex');\n const provenance = `${JSON.stringify(buildExportProvenance(bundleSha256), null, 2)}\\n`;\n\n return { bundle, provenance, bundleSha256 };\n}\n\nconst MAX_INLINE_WIDTH = 100;\n\nfunction render(value: unknown, depth: number): string\n{\n const inline = JSON.stringify(value);\n if (inline === undefined)\n {\n throw new Error('the contract bundle carries a value JSON cannot represent');\n }\n const pad = ' '.repeat(depth);\n if (inline.length + pad.length <= MAX_INLINE_WIDTH || typeof value !== 'object' || value === null)\n {\n return spaced(inline);\n }\n\n const inner = ' '.repeat(depth + 1);\n if (Array.isArray(value))\n {\n const items = value.map((item) => `${inner}${render(item, depth + 1)}`);\n\n return `[\\n${items.join(',\\n')}\\n${pad}]`;\n }\n\n const members = Object.entries(value as Record<string, unknown>)\n .map(([key, member]) => `${inner}${JSON.stringify(key)}: ${render(member, depth + 1)}`);\n\n return `{\\n${members.join(',\\n')}\\n${pad}}`;\n}\n\n/** `{\"a\":1}` → `{ \"a\": 1 }` — the inline form used inside the indented one. */\nfunction spaced(inline: string): string\n{\n if (!inline.startsWith('{') && !inline.startsWith('['))\n {\n return inline;\n }\n let out = '';\n let inString = false;\n let escaped = false;\n for (const ch of inline)\n {\n if (escaped)\n {\n out += ch;\n escaped = false;\n continue;\n }\n if (ch === '\\\\' && inString)\n {\n out += ch;\n escaped = true;\n continue;\n }\n if (ch === '\"')\n {\n inString = !inString;\n out += ch;\n continue;\n }\n if (inString)\n {\n out += ch;\n continue;\n }\n out += separatorFor(ch);\n }\n\n return out;\n}\n\nfunction separatorFor(ch: string): string\n{\n if (ch === ':' || ch === ',')\n {\n return `${ch} `;\n }\n if (ch === '{')\n {\n return '{ ';\n }\n if (ch === '}')\n {\n return ' }';\n }\n\n return ch;\n}\n","/**\n * @spfn/auth - Shared Types\n *\n * Common types and constants used across the auth package\n */\n\n/**\n * Supported JWT signature algorithms\n *\n * - ES256: ECDSA with P-256 and SHA-256 (recommended, smaller keys)\n * - RS256: RSA with SHA-256 (fallback, larger keys)\n */\nexport const KEY_ALGORITHM = ['ES256', 'RS256'] as const;\n\n/**\n * Key algorithm type derived from the const array\n */\nexport type KeyAlgorithmType = typeof KEY_ALGORITHM[number];\n\n/**\n * Where a registered key lives, as the client declares it.\n *\n * Only for telling one entry apart from another in the key list — nothing is\n * authorized or refused by it, so a client that lies gains nothing. Stored via\n * `enumText`, so adding a value here needs no migration.\n */\nexport const KEY_PLATFORM = ['ios', 'android', 'web', 'desktop'] as const;\n\n/**\n * Key platform type derived from the const array\n */\nexport type KeyPlatformType = typeof KEY_PLATFORM[number];\n\n/** Longest device label accepted at registration, and what the list returns. */\nexport const KEY_DEVICE_NAME_MAX_LENGTH = 64;\n\n/**\n * Invitation status enum values\n * Single source of truth for all invitation statuses\n */\nexport const INVITATION_STATUSES = ['pending', 'accepted', 'expired', 'cancelled'] as const;\n\n/**\n * Invitation status type derived from the const array\n */\nexport type InvitationStatus = typeof INVITATION_STATUSES[number];\n\n/**\n * User status enum values\n * Single source of truth for all user statuses\n *\n * - active: Normal operation (default)\n * - inactive: Deactivated (user request, dormant)\n * - suspended: Locked (security incident, ToS violation)\n * - pending_deletion: Deletion requested, within the grace period (recoverable)\n * - deleted: Grace period elapsed and the account was purged (anonymize mode only —\n * hard-delete removes the row instead, so this status never appears for it)\n */\nexport const USER_STATUSES = ['active', 'inactive', 'suspended', 'pending_deletion', 'deleted'] as const;\n\n/**\n * User status type derived from the const array\n */\nexport type UserStatus = typeof USER_STATUSES[number];\n\n/**\n * Social provider enum values\n * Single source of truth for supported OAuth providers\n */\nexport const SOCIAL_PROVIDERS = ['google', 'apple', 'github', 'kakao', 'naver', 'superself'] as const;\n\n/**\n * Social provider type derived from the const array\n */\nexport type SocialProvider = typeof SOCIAL_PROVIDERS[number];\n\n/**\n * Account deletion request status enum values\n * Single source of truth for `account_deletion_requests.status`\n *\n * - pending: Awaiting the grace period (or immediate purge)\n * - cancelled: User (or admin) recovered the account before purge\n * - completed: The purge ran (row is kept as an audit record, never deleted)\n */\nexport const ACCOUNT_DELETION_REQUEST_STATUSES = ['pending', 'cancelled', 'completed'] as const;\n\n/**\n * Account deletion request status type derived from the const array\n */\nexport type AccountDeletionRequestStatus = typeof ACCOUNT_DELETION_REQUEST_STATUSES[number];\n\n/**\n * Who initiated an account deletion request\n */\nexport const ACCOUNT_DELETION_REQUESTED_BY = ['self', 'admin'] as const;\n\n/**\n * Account deletion requester type derived from the const array\n */\nexport type AccountDeletionRequestedBy = typeof ACCOUNT_DELETION_REQUESTED_BY[number];\n\n/**\n * Purge strategy enum values\n *\n * - anonymize: Scrub PII, keep the row (status becomes 'deleted') — default\n * - hard-delete: Physically remove the `users` row (cascades to child rows)\n */\nexport const PURGE_STRATEGIES = ['anonymize', 'hard-delete'] as const;\n\n/**\n * Purge strategy type derived from the const array\n */\nexport type PurgeStrategy = typeof PURGE_STRATEGIES[number];\n","/**\n * The header names each end announces itself under.\n *\n * Separated from the logic that reads them so the contract bundle can name them\n * without importing the version comparison, which reads the bundle back. These\n * are declarations and depend on nothing.\n *\n * @module server/client-proof/wire-headers\n */\n\n/** What a client says about itself, one header each. */\nexport const CLIENT_IDENTITY_HEADERS = {\n kind: 'x-spfn-client-kind',\n version: 'x-spfn-client-version',\n contractVersion: 'x-spfn-client-contract-version',\n} as const;\n\n/**\n * What the server says about itself, on every response.\n *\n * Distinct names from the request headers on purpose: a proxy that echoes a\n * request header into the response would otherwise make the client's own\n * version look like the server's.\n */\nexport const SERVER_CONTRACT_HEADERS = {\n version: 'x-spfn-server-contract-version',\n supportedRange: 'x-spfn-supported-contract-range',\n} as const;\n\n/**\n * The client kinds the server distinguishes.\n *\n * `web` is separated from the two app kinds because it carries no contract\n * version: a browser bundle is deployed with the server that serves it, so\n * there is no second version to reconcile.\n */\nexport const CLIENT_KINDS = ['web', 'ios', 'android'] as const;\n\nexport type ClientKind = typeof CLIENT_KINDS[number];\n\n/** A kind that ships independently of the server, so its contract version matters. */\nexport function isAppKind(kind: ClientKind): boolean\n{\n return kind !== 'web';\n}\n","/**\n * What each end announces about itself, and what the server does with it.\n *\n * A client compiled and shipped separately from the server — a mobile app in a\n * store, a browser tab left open for a week — cannot be fixed by redeploying.\n * Until now a mismatch between what that client was built against and what the\n * server serves surfaced as an undecodable body: the app looked broken and\n * nothing said why.\n *\n * Both ends now say what they are. The client names its kind, its own release\n * and the contract version it was generated from; the server answers with the\n * contract version it serves and the range it accepts. Neither statement enters\n * the proof input — this is diagnostic, not something under authentication, and\n * `PROOF_INPUT_FIELDS` is unchanged.\n *\n * The server states facts and refuses what it cannot serve. It does not tell a\n * client to update: comparing its own version against the announced range and\n * deciding what the user should see is the client's judgment, made in the client.\n *\n * @module server/client-proof/wire-version\n */\nimport { CONTRACT_MAJOR, CONTRACT_SUPPORTED_RANGE, CONTRACT_VERSION } from './contract-bundle';\nimport { ClientProofRefusal } from './refusal';\nimport {\n CLIENT_IDENTITY_HEADERS,\n CLIENT_KINDS,\n isAppKind,\n SERVER_CONTRACT_HEADERS,\n type ClientKind,\n} from './wire-headers';\n\nexport {\n CLIENT_IDENTITY_HEADERS,\n CLIENT_KINDS,\n isAppKind,\n SERVER_CONTRACT_HEADERS,\n type ClientKind,\n} from './wire-headers';\n\n/** What one request announced about the client that sent it. */\nexport interface ClientIdentity\n{\n kind: ClientKind;\n\n /** The client's own release — a store version, or a bundle build. */\n version: string | null;\n\n /** The contract version the client was generated from. Never set for `web`. */\n contractVersion: string | null;\n}\n\n/**\n * Reads the identity headers, or null when the kind is absent or unrecognised.\n *\n * Null is not by itself a refusal — a request from something that predates\n * these headers reaches here too. `judgeClientIdentity` decides.\n */\nexport function readClientIdentity(headers: Headers): ClientIdentity | null\n{\n const kind = headers.get(CLIENT_IDENTITY_HEADERS.kind);\n if (kind === null || !isClientKind(kind))\n {\n return null;\n }\n\n return {\n kind,\n version: headers.get(CLIENT_IDENTITY_HEADERS.version),\n contractVersion: headers.get(CLIENT_IDENTITY_HEADERS.contractVersion),\n };\n}\n\nfunction isClientKind(value: string): value is ClientKind\n{\n return (CLIENT_KINDS as readonly string[]).includes(value);\n}\n\n/**\n * Whether the server serves what the client was generated against.\n *\n * Under 0.x the minor carries breaking changes, so a supported client agrees on\n * major and minor. From 1.0.0 the major alone decides. This is the rule\n * `CONTRACT_SUPPORTED_RANGE` spells out; keeping it as a comparison rather than\n * parsing that string leaves one place to change when the line reaches 1.0.0.\n */\nexport function isContractVersionSupported(clientVersion: string): boolean\n{\n const client = parseVersion(clientVersion);\n if (client === null)\n {\n return false;\n }\n const server = parseVersion(CONTRACT_VERSION);\n if (server === null || client.major !== server.major)\n {\n return false;\n }\n\n return CONTRACT_MAJOR > 0 || client.minor === server.minor;\n}\n\nfunction parseVersion(raw: string): { major: number; minor: number } | null\n{\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:[-+].*)?$/.exec(raw);\n if (match === null)\n {\n return null;\n }\n\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\n/**\n * The refusal a request's announced identity earns, or null to let it through.\n *\n * An app kind must state a contract version this server serves. A version it\n * does not serve, and the absence of one, are the same answer: the two ends do\n * not agree on what the contract is, which is what CONTRACT_UNSUPPORTED means.\n * The response carries the server's version and range, so the client can say\n * which way the gap runs.\n *\n * `web` is exempt from the contract check by construction, not by leniency.\n *\n * A request with no recognised kind passes. The check is on what a client says\n * about itself, and a caller that says nothing — a curl, a health probe, a\n * server-to-server call — is not a deployed client this rule is about.\n */\nexport function judgeClientIdentity(identity: ClientIdentity | null): ClientProofRefusal | null\n{\n if (identity === null || !isAppKind(identity.kind))\n {\n return null;\n }\n if (identity.contractVersion === null)\n {\n return ClientProofRefusal.contractVersionMissing();\n }\n if (!isContractVersionSupported(identity.contractVersion))\n {\n return ClientProofRefusal.contractVersionUnsupported();\n }\n\n return null;\n}\n\n/** Writes the server's own announcement onto a response's headers. */\nexport function applyServerContractHeaders(headers: Headers): void\n{\n headers.set(SERVER_CONTRACT_HEADERS.version, CONTRACT_VERSION);\n headers.set(SERVER_CONTRACT_HEADERS.supportedRange, CONTRACT_SUPPORTED_RANGE);\n}\n\n/** The same announcement as a plain object, for a response built from one. */\nexport function serverContractHeaders(): Record<string, string>\n{\n return {\n [SERVER_CONTRACT_HEADERS.version]: CONTRACT_VERSION,\n [SERVER_CONTRACT_HEADERS.supportedRange]: CONTRACT_SUPPORTED_RANGE,\n };\n}\n","/**\n * The mobile-contract dev surface: a fetch-style handler exposing the three\n * dev operations (handshake / echo.send / items.list) plus the `/control`\n * test hooks the spfn-mobile integration suites drive.\n *\n * Framework-free on purpose — `fetch(request) => Response` plugs into\n * `@hono/node-server`'s `serve({ fetch })` or any Web-standard runtime, and\n * the contract needs byte-exact control over bodies and envelopes that a\n * validating router would take away.\n *\n * This is a dev/test surface, not a production deployment target: keys are\n * injected at construction, state is in-memory, and `/control` mutates it.\n *\n * @module server/client-proof/dev-handler\n */\nimport { encodeCanonicalJson, type CanonicalValue } from './canonical-json';\nimport { admitClientProofRequest, type Admission } from './admission';\nimport {\n CONTRACT_OPERATIONS,\n ContractTypeError,\n decodeEchoRequest,\n decodeHandshakeRequest,\n decodeListItemsRequest,\n encodeEchoResponse,\n encodeHandshakeResponse,\n encodeListItemsResponse,\n type ContractItem,\n type ContractOperation,\n type ListItemsRequest,\n} from './contract-types';\nimport { ClientProofRefusal, newHexId } from './refusal';\nimport { ClientProofState, type ClientProofStateOptions, TestClock } from './state';\nimport { handleControlRequest, CONTROL_PREFIX } from './dev-control';\nimport { serverContractHeaders } from './wire-version';\n\n/** Far above any contract request and far below anything worth buffering. */\nconst MAX_BODY_BYTES = 1 << 20;\n\nconst HTTP_OK = 200;\n\n/**\n * The items `items.list` pages through — fixed and small on purpose, matching\n * the spfn-mobile reference catalogue byte for byte so an integration test can\n * assert exact values against either server.\n */\nexport const DEV_CATALOGUE: readonly ContractItem[] = [\n { id: 'item-0001', name: 'alpha', updatedAtMillis: 1_750_000_000_001n },\n { id: 'item-0002', name: 'bravo', updatedAtMillis: 1_750_000_000_002n },\n { id: 'item-0003', name: 'charlie', updatedAtMillis: 1_750_000_000_003n },\n { id: 'item-0004', name: 'delta', updatedAtMillis: 1_750_000_000_004n },\n { id: 'item-0005', name: 'echo', updatedAtMillis: 1_750_000_000_005n },\n];\n\n/** The largest `items.list` page this server will answer with. */\nexport const DEV_MAX_LIMIT = 100n;\n\nexport interface ClientProofDevHandlerOptions extends ClientProofStateOptions\n{\n /**\n * Token the `/control` routes require (header `x-spfn-reference-control`).\n * Generated per construction when omitted; never logged.\n */\n controlToken?: string;\n\n /** Disables the `/control` surface entirely. @default true */\n enableControl?: boolean;\n\n /** One line per request: method, path, status. Nothing a request carried. */\n log?: (line: string) => void;\n}\n\nexport interface ClientProofDevHandler\n{\n fetch(request: Request): Promise<Response>;\n state: ClientProofState;\n controlToken: string;\n}\n\nexport function createClientProofDevHandler(options: ClientProofDevHandlerOptions): ClientProofDevHandler\n{\n const state = new ClientProofState(options);\n const controlToken = options.controlToken ?? newHexId();\n const enableControl = options.enableControl ?? true;\n const log = options.log ?? (() => undefined);\n\n async function dispatch(request: Request): Promise<Response>\n {\n state.recordRequest();\n const url = new URL(request.url);\n\n if (enableControl && url.pathname.startsWith(CONTROL_PREFIX))\n {\n return handleControlRequest(state, controlToken, url.pathname, request);\n }\n\n // A query string is refused by omission: no contract path carries one,\n // and a proof is taken over the path alone.\n const operation = url.search === ''\n ? CONTRACT_OPERATIONS.find((op) => op.path === url.pathname && op.method === request.method)\n : undefined;\n if (operation === undefined)\n {\n return refuse(ClientProofRefusal.unroutable());\n }\n\n const body = await readBodyCapped(request);\n if (body === null)\n {\n return refuse(ClientProofRefusal.bodyTooLarge());\n }\n\n // Before verification, so a request a test is holding open has not\n // spent its nonce by the time the client gives up waiting for it.\n await waitOutHold(url.pathname);\n\n const admission = admitClientProofRequest({\n state,\n headers: request.headers,\n method: operation.method,\n path: operation.path,\n requiresSession: operation.requiresSession,\n body,\n });\n if (!admission.admitted)\n {\n return refuse(admission.refusal);\n }\n\n return apply(operation, admission);\n }\n\n function apply(operation: ContractOperation, admission: Extract<Admission, { admitted: true }>): Response\n {\n let value: CanonicalValue;\n try\n {\n if (operation.id === 'auth.clientProof.handshake')\n {\n const request = decodeHandshakeRequest(admission.value);\n // The proof already binds the header identity to the key that\n // signed it, so a body naming a different client is a request\n // whose two halves disagree about who sent it.\n if (request.clientId !== admission.credentials.clientId\n || request.keyId !== admission.credentials.keyId)\n {\n return refuse(ClientProofRefusal.bodyNotTheDeclaredType());\n }\n const opened = state.openSession(request.clientId, request.keyId);\n value = encodeHandshakeResponse(opened.sessionId, BigInt(opened.expiresAtMillis));\n }\n else if (operation.id === 'echo.send')\n {\n const request = decodeEchoRequest(admission.value);\n value = encodeEchoResponse(request.message, request.sequence, BigInt(state.nowMillis()));\n }\n else\n {\n const listed = listItems(decodeListItemsRequest(admission.value));\n if (listed === null)\n {\n return refuse(ClientProofRefusal.bodyNotTheDeclaredType());\n }\n value = listed;\n }\n }\n catch (error)\n {\n if (error instanceof ContractTypeError)\n {\n return refuse(ClientProofRefusal.bodyNotTheDeclaredType());\n }\n\n return refuse(ClientProofRefusal.unprocessable());\n }\n\n state.recordOperation(operation.id);\n\n return contractResponse(HTTP_OK, encodeCanonicalJson(value));\n }\n\n function refuse(refusal: ClientProofRefusal): Response\n {\n state.recordRefusal();\n\n return contractResponse(refusal.httpStatus, refusal.envelopeBytes(newHexId()));\n }\n\n async function waitOutHold(path: string): Promise<void>\n {\n const millis = state.takeHoldMillis(path);\n if (millis > 0)\n {\n await new Promise((resolve) => setTimeout(resolve, millis));\n }\n }\n\n return {\n state,\n controlToken,\n fetch: async (request: Request): Promise<Response> =>\n {\n try\n {\n const response = await dispatch(request);\n log(`${request.method} ${new URL(request.url).pathname} -> ${response.status}`);\n\n return response;\n }\n catch\n {\n // A contract answer rather than a stack trace: an exception\n // message can quote the request that produced it.\n return refuse(ClientProofRefusal.unprocessable());\n }\n },\n };\n}\n\n/**\n * One page of the catalogue, or null when the request is not one this\n * contract describes. An unknown cursor and a limit outside 1…MAX are refused\n * rather than clamped — a server that quietly repaired a request would hide\n * the client bug that produced it.\n */\nfunction listItems(request: ListItemsRequest): CanonicalValue | null\n{\n if (request.limit < 1n || request.limit > DEV_MAX_LIMIT)\n {\n return null;\n }\n let start = 0;\n if (request.cursor !== undefined)\n {\n const index = DEV_CATALOGUE.findIndex((item) => item.id === request.cursor);\n if (index < 0)\n {\n return null;\n }\n start = index + 1;\n }\n const end = Math.min(DEV_CATALOGUE.length, start + Number(request.limit));\n const page = DEV_CATALOGUE.slice(start, end);\n // Present only when a further page exists, so \"nextCursor is absent\" is a\n // fact about the data rather than a value the client has to interpret.\n const nextCursor = end < DEV_CATALOGUE.length && page.length > 0 ? page[page.length - 1].id : null;\n\n return encodeListItemsResponse([...page], nextCursor);\n}\n\nfunction contractResponse(status: number, body: Uint8Array): Response\n{\n return new Response(toArrayBuffer(body), {\n status,\n headers: { 'content-type': 'application/json', ...serverContractHeaders() },\n });\n}\n\n/** The body, or null when it is larger than this server will read. */\nasync function readBodyCapped(request: Request): Promise<Uint8Array | null>\n{\n if (request.body === null)\n {\n return new Uint8Array(0);\n }\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;)\n {\n const { done, value } = await reader.read();\n if (done)\n {\n break;\n }\n total += value.length;\n if (total > MAX_BODY_BYTES)\n {\n await reader.cancel();\n\n return null;\n }\n chunks.push(value);\n }\n const body = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks)\n {\n body.set(chunk, offset);\n offset += chunk.length;\n }\n\n return body;\n}\n\nfunction toArrayBuffer(bytes: Uint8Array): ArrayBuffer\n{\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nexport { TestClock };\n","/**\n * One place turns a clientProofV1 refusal into a response.\n *\n * A proven call is answered by a generated SDK that classifies a failure by\n * `error.code` alone and refuses a code it does not know. So a refusal must\n * leave this server as the contract's own envelope — the canonical bytes of\n * `{\"error\":{\"code\",\"message\",\"requestId\"}}` carrying one of the six refusal\n * codes — and nothing else. Routing a refusal through the generic error\n * handler instead puts the wrapper error class's name in `error.code`\n * (`UnauthorizedError`), which no SDK can classify (#106).\n *\n * Every refusal surface (the guard, the profile middleware) builds its answer\n * here rather than assembling one of its own, so a code path added later\n * cannot reintroduce a body that says something else.\n *\n * hono is imported as types only, so this module adds no runtime dependency.\n *\n * @module server/client-proof/refusal-response\n */\nimport type { Context } from 'hono';\n\nimport { newHexId, type ClientProofRefusal } from './refusal';\nimport { serverContractHeaders } from './wire-version';\n\n/**\n * The canonical contract envelope for one refusal, with the server's contract\n * announcement — a refused client needs the range most.\n */\nexport function clientProofRefusalResponse(c: Context, refusal: ClientProofRefusal): Response\n{\n const bytes = refusal.envelopeBytes(newHexId());\n const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n\n return c.newResponse(buffer, refusal.httpStatus as 401, {\n 'content-type': 'application/json',\n ...serverContractHeaders(),\n });\n}\n","/**\n * Hono middleware adapter for clientProofV1 — the `requiresSession` guard for\n * SPFN servers that mount contract operations as ordinary routes.\n *\n * Runs the full admission sequence over the raw request bytes and, on\n * acceptance, tags the request `clientType: 'mobile'` (the attestation slot\n * PROXY-BACKEND-AUTH-SPEC reserved) and exposes the parsed canonical body and\n * credentials under the `clientProof` context key.\n *\n * hono is imported as types only — the middleware itself is a plain async\n * function, so this module adds no runtime dependency.\n *\n * @module server/client-proof/guard\n */\nimport type { Context, MiddlewareHandler, Next } from 'hono';\n\nimport { admitClientProofRequest, type ClientProofCredentials } from './admission';\nimport type { CanonicalValue } from './canonical-json';\nimport { clientProofRefusalResponse } from './refusal-response';\nimport type { ClientProofState } from './state';\n\n/** What the guard leaves in the context for the route handler. */\nexport interface ClientProofContext\n{\n credentials: ClientProofCredentials;\n\n /** The request body as a canonical value (already byte-verified). */\n value: CanonicalValue;\n}\n\nexport interface ClientProofGuardOptions\n{\n /**\n * The contract path the client signed, when it differs from the mounted\n * path (e.g. behind a stripped ingress prefix). Defaults to the request\n * path.\n */\n contractPath?: string;\n}\n\n/**\n * A guard for operations with `requiresSession: true`.\n *\n * Refusals are answered with the contract envelope and never reach the route.\n */\nexport function createClientProofGuard(\n state: ClientProofState,\n options: ClientProofGuardOptions = {},\n): MiddlewareHandler\n{\n return async (c: Context, next: Next) =>\n {\n const body = new Uint8Array(await c.req.arrayBuffer());\n const admission = admitClientProofRequest({\n state,\n headers: c.req.raw.headers,\n method: c.req.method,\n path: options.contractPath ?? c.req.path,\n requiresSession: true,\n body,\n });\n if (!admission.admitted)\n {\n state.recordRefusal();\n\n return clientProofRefusalResponse(c, admission.refusal);\n }\n c.set('clientType', 'mobile');\n c.set('clientProof', {\n credentials: admission.credentials,\n value: admission.value,\n } satisfies ClientProofContext);\n await next();\n\n return undefined;\n };\n}\n","/**\n * The version announcement, applied to every request rather than to the proven\n * ones.\n *\n * Enrollment and login are the first calls a client makes and they carry no\n * proof — there is no key to sign with yet. A check that lives inside proof\n * admission therefore never sees the client it is meant to catch: an outdated\n * app fails at login, before it reaches anything proven. This runs ahead of all\n * of it.\n *\n * hono is imported as types only, so this module adds no runtime dependency.\n *\n * @module server/client-proof/version-middleware\n */\nimport type { Context, MiddlewareHandler, Next } from 'hono';\n\nimport { newHexId } from './refusal';\nimport { applyServerContractHeaders, judgeClientIdentity, readClientIdentity, type ClientIdentity } from './wire-version';\n\n/** The context key the identity is left under, for a handler that wants it. */\nexport const CLIENT_IDENTITY_CONTEXT_KEY = 'clientIdentity';\n\n/**\n * What the client said about itself on this request, or null.\n *\n * Null covers two cases that behave the same downstream: this middleware is not\n * mounted, and it is mounted but the request named no client kind. Neither is an\n * error — an app that predates the headers is still a working app.\n */\nexport function readContextClientIdentity(c: Context): ClientIdentity | null\n{\n return (c.get(CLIENT_IDENTITY_CONTEXT_KEY) as ClientIdentity | undefined) ?? null;\n}\n\n/**\n * Announces the server's contract version on every response and refuses a\n * client whose own contract version this server does not serve.\n *\n * The announcement goes out either way. A refused client needs it most — the\n * refusal says the two ends disagree, and the range is what says how.\n *\n * Mount this before authentication, not after: the point is to answer a stale\n * client before anything else has a chance to fail confusingly.\n */\nexport function createClientVersionMiddleware(): MiddlewareHandler\n{\n return async (c: Context, next: Next) =>\n {\n const identity = readClientIdentity(c.req.raw.headers);\n const refusal = judgeClientIdentity(identity);\n if (refusal !== null)\n {\n const bytes = refusal.envelopeBytes(newHexId());\n const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n const response = c.newResponse(buffer, refusal.httpStatus as 409, {\n 'content-type': 'application/json',\n });\n applyServerContractHeaders(response.headers);\n\n return response;\n }\n if (identity !== null)\n {\n c.set(CLIENT_IDENTITY_CONTEXT_KEY, identity satisfies ClientIdentity);\n }\n await next();\n applyServerContractHeaders(c.res.headers);\n\n return undefined;\n };\n}\n"],"mappings":"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","names":["text","members","hold","INT64_MIN","INT64_MAX","members","required","optional","createHash","HTTP_OK"]}
|
|
1
|
+
{"version":3,"sources":["../src/server/client-proof/canonical-json.ts","../src/server/client-proof/proof.ts","../src/server/client-proof/refusal.ts","../src/server/client-proof/replay-store.ts","../src/server/client-proof/state.ts","../src/server/client-proof/admission.ts","../src/server/client-proof/contract-types.ts","../src/server/client-proof/dev-control.ts","../src/server/client-proof/contract-bundle.ts","../src/server/types.ts","../src/server/client-proof/wire-headers.ts","../src/server/client-proof/wire-version.ts","../src/server/client-proof/dev-handler.ts","../src/server/client-proof/refusal-response.ts","../src/server/client-proof/guard.ts","../src/server/client-proof/version-middleware.ts"],"sourcesContent":["/**\n * SPFN-CANON-JSON-1 — the canonical JSON form the mobile contract pins.\n *\n * The rules (contracts/mobile/spfn-mobile-contract.json `canonicalJson`):\n * - object keys sorted ascending by UTF-8 byte sequence\n * - no insignificant whitespace\n * - numbers are signed 64-bit integers only\n * - string escapes: `\"` and `\\` escaped; C0 controls use \\b \\f \\n \\r \\t where\n * defined and lowercase \\u00XX otherwise; every other scalar is emitted\n * literally as UTF-8\n * - absent optional fields are omitted, never null\n *\n * JSON.parse cannot implement this: it loses int64 precision, accepts duplicate\n * keys and (in V8) raw control characters, so both directions are hand-rolled.\n * A proof binds the received bytes — parse-then-re-encode equality is what makes\n * canonicity a rule a client can actually break.\n *\n * @module server/client-proof/canonical-json\n */\n\nexport type CanonicalObject = Map<string, CanonicalValue>;\n\nexport type CanonicalValue = null | boolean | bigint | string | CanonicalValue[] | CanonicalObject;\n\n/**\n * Parse failures carry the code the mobile conformance fixtures name\n * (Contracts/fixtures/canonical/rejects.json), so the fixtures can assert on it.\n */\nexport type CanonicalJsonErrorCode =\n | 'DUPLICATE_KEY'\n | 'NON_INTEGER_NUMBER'\n | 'TRAILING_CONTENT'\n | 'UNEXPECTED_END'\n | 'INVALID_TOKEN'\n | 'INVALID_ESCAPE'\n | 'INTEGER_OUT_OF_RANGE'\n | 'INVALID_UTF8';\n\nexport class CanonicalJsonError extends Error\n{\n constructor(readonly code: CanonicalJsonErrorCode)\n {\n super(`canonical JSON: ${code}`);\n this.name = 'CanonicalJsonError';\n }\n}\n\nconst INT64_MIN = -(2n ** 63n);\nconst INT64_MAX = 2n ** 63n - 1n;\n\n// ============================================================================\n// Parsing\n// ============================================================================\n\n/**\n * Parse bytes as SPFN-CANON-JSON-1.\n *\n * Arbitrary whitespace and key order are accepted here — parsing alone proves\n * nothing about canonicity. Callers that must enforce it re-encode the result\n * and compare bytes (see `isCanonicalBytes`).\n */\nexport function parseCanonicalJson(bytes: Uint8Array): CanonicalValue\n{\n let text: string;\n try\n {\n text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);\n }\n catch\n {\n throw new CanonicalJsonError('INVALID_UTF8');\n }\n\n const parser = new Parser(text);\n const value = parser.parseValue();\n parser.skipWhitespace();\n if (!parser.atEnd())\n {\n throw new CanonicalJsonError('TRAILING_CONTENT');\n }\n\n return value;\n}\n\n/** True when `bytes` are exactly the canonical encoding of the value they parse to. */\nexport function isCanonicalBytes(bytes: Uint8Array, value: CanonicalValue): boolean\n{\n const encoded = encodeCanonicalJson(value);\n if (encoded.length !== bytes.length)\n {\n return false;\n }\n for (let i = 0; i < encoded.length; i++)\n {\n if (encoded[i] !== bytes[i])\n {\n return false;\n }\n }\n\n return true;\n}\n\nclass Parser\n{\n private pos = 0;\n\n constructor(private readonly text: string) \n {}\n\n atEnd(): boolean\n {\n return this.pos >= this.text.length;\n }\n\n skipWhitespace(): void\n {\n while (!this.atEnd())\n {\n const c = this.text[this.pos];\n if (c === ' ' || c === '\\t' || c === '\\n' || c === '\\r')\n {\n this.pos++;\n continue;\n }\n break;\n }\n }\n\n parseValue(): CanonicalValue\n {\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const c = this.text[this.pos];\n if (c === '{')\n {\n return this.parseObject();\n }\n if (c === '[')\n {\n return this.parseArray();\n }\n if (c === '\"')\n {\n return this.parseString();\n }\n if (c === '-' || (c >= '0' && c <= '9'))\n {\n return this.parseNumber();\n }\n if (this.text.startsWith('null', this.pos))\n {\n this.pos += 4;\n\n return null;\n }\n if (this.text.startsWith('true', this.pos))\n {\n this.pos += 4;\n\n return true;\n }\n if (this.text.startsWith('false', this.pos))\n {\n this.pos += 5;\n\n return false;\n }\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n\n private parseObject(): CanonicalObject\n {\n this.pos++; // '{'\n const members: CanonicalObject = new Map();\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] === '}')\n {\n this.pos++;\n\n return members;\n }\n for (;;)\n {\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] !== '\"')\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n const key = this.parseString();\n if (members.has(key))\n {\n throw new CanonicalJsonError('DUPLICATE_KEY');\n }\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] !== ':')\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n this.pos++;\n members.set(key, this.parseValue());\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const next = this.text[this.pos];\n if (next === ',')\n {\n this.pos++;\n continue;\n }\n if (next === '}')\n {\n this.pos++;\n\n return members;\n }\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n }\n\n private parseArray(): CanonicalValue[]\n {\n this.pos++; // '['\n const items: CanonicalValue[] = [];\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n if (this.text[this.pos] === ']')\n {\n this.pos++;\n\n return items;\n }\n for (;;)\n {\n items.push(this.parseValue());\n this.skipWhitespace();\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const next = this.text[this.pos];\n if (next === ',')\n {\n this.pos++;\n continue;\n }\n if (next === ']')\n {\n this.pos++;\n\n return items;\n }\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n }\n\n private parseString(): string\n {\n this.pos++; // '\"'\n let out = '';\n for (;;)\n {\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const c = this.text[this.pos];\n const code = this.text.charCodeAt(this.pos);\n if (c === '\"')\n {\n this.pos++;\n\n return out;\n }\n if (c === '\\\\')\n {\n out += this.parseEscape();\n continue;\n }\n if (code < 0x20)\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n out += c;\n this.pos++;\n }\n }\n\n private parseEscape(): string\n {\n this.pos++; // '\\'\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const c = this.text[this.pos];\n this.pos++;\n switch (c)\n {\n case '\"': return '\"';\n case '\\\\': return '\\\\';\n case '/': return '/';\n case 'b': return '\\b';\n case 'f': return '\\f';\n case 'n': return '\\n';\n case 'r': return '\\r';\n case 't': return '\\t';\n case 'u': return this.parseUnicodeEscape();\n default: throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n }\n\n private parseUnicodeEscape(): string\n {\n const high = this.readHex4();\n if (high >= 0xdc00 && high <= 0xdfff)\n {\n // A low surrogate with no preceding high surrogate.\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n if (high < 0xd800 || high > 0xdbff)\n {\n return String.fromCharCode(high);\n }\n // A high surrogate must be completed by an escaped low surrogate.\n if (this.text[this.pos] !== '\\\\' || this.text[this.pos + 1] !== 'u')\n {\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n this.pos += 2;\n const low = this.readHex4();\n if (low < 0xdc00 || low > 0xdfff)\n {\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n\n return String.fromCharCode(high, low);\n }\n\n private readHex4(): number\n {\n if (this.pos + 4 > this.text.length)\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const hex = this.text.slice(this.pos, this.pos + 4);\n if (!/^[0-9a-fA-F]{4}$/.test(hex))\n {\n throw new CanonicalJsonError('INVALID_ESCAPE');\n }\n this.pos += 4;\n\n return parseInt(hex, 16);\n }\n\n private parseNumber(): bigint\n {\n const start = this.pos;\n if (this.text[this.pos] === '-')\n {\n this.pos++;\n }\n if (this.atEnd())\n {\n throw new CanonicalJsonError('UNEXPECTED_END');\n }\n const first = this.text[this.pos];\n if (first < '0' || first > '9')\n {\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n if (first === '0')\n {\n this.pos++;\n }\n else\n {\n while (!this.atEnd() && this.text[this.pos] >= '0' && this.text[this.pos] <= '9')\n {\n this.pos++;\n }\n }\n if (!this.atEnd())\n {\n const next = this.text[this.pos];\n if (next >= '0' && next <= '9')\n {\n // A leading zero followed by more digits.\n throw new CanonicalJsonError('INVALID_TOKEN');\n }\n if (next === '.' || next === 'e' || next === 'E')\n {\n throw new CanonicalJsonError('NON_INTEGER_NUMBER');\n }\n }\n const value = BigInt(this.text.slice(start, this.pos));\n if (value < INT64_MIN || value > INT64_MAX)\n {\n throw new CanonicalJsonError('INTEGER_OUT_OF_RANGE');\n }\n\n return value;\n }\n}\n\n// ============================================================================\n// Encoding\n// ============================================================================\n\n/** Encode a value as SPFN-CANON-JSON-1 bytes. */\nexport function encodeCanonicalJson(value: CanonicalValue): Uint8Array\n{\n return new TextEncoder().encode(encodeToString(value));\n}\n\nfunction encodeToString(value: CanonicalValue): string\n{\n if (value === null)\n {\n return 'null';\n }\n if (typeof value === 'boolean')\n {\n return value ? 'true' : 'false';\n }\n if (typeof value === 'bigint')\n {\n return value.toString();\n }\n if (typeof value === 'string')\n {\n return encodeString(value);\n }\n if (Array.isArray(value))\n {\n return `[${value.map(encodeToString).join(',')}]`;\n }\n const keys = [...value.keys()].sort(compareByCodePoints);\n const members = keys.map((key) => `${encodeString(key)}:${encodeToString(value.get(key)!)}`);\n\n return `{${members.join(',')}}`;\n}\n\n/**\n * UTF-8 byte order equals code point order, so keys are compared by code\n * points rather than UTF-16 code units (which would misorder U+E000..U+FFFF\n * against supplementary-plane characters).\n */\nfunction compareByCodePoints(a: string, b: string): number\n{\n let i = 0;\n let j = 0;\n while (i < a.length && j < b.length)\n {\n const ca = a.codePointAt(i)!;\n const cb = b.codePointAt(j)!;\n if (ca !== cb)\n {\n return ca - cb;\n }\n i += ca > 0xffff ? 2 : 1;\n j += cb > 0xffff ? 2 : 1;\n }\n\n return (a.length - i) - (b.length - j);\n}\n\nfunction encodeString(value: string): string\n{\n let out = '\"';\n for (const ch of value)\n {\n const code = ch.codePointAt(0)!;\n if (ch === '\"')\n {\n out += '\\\\\"';\n }\n else if (ch === '\\\\')\n {\n out += '\\\\\\\\';\n }\n else if (code === 0x08)\n {\n out += '\\\\b';\n }\n else if (code === 0x0c)\n {\n out += '\\\\f';\n }\n else if (code === 0x0a)\n {\n out += '\\\\n';\n }\n else if (code === 0x0d)\n {\n out += '\\\\r';\n }\n else if (code === 0x09)\n {\n out += '\\\\t';\n }\n else if (code < 0x20)\n {\n out += `\\\\u00${code.toString(16).padStart(2, '0')}`;\n }\n else\n {\n out += ch;\n }\n }\n\n return out + '\"';\n}\n","/**\n * SPFN-PROOF-INPUT-1 — proof-input assembly and verification for clientProofV1.\n *\n * The proof input is 8 fields joined by `\\n` in fixed order: profile, method,\n * path, clientId, keyId, nonce, issuedAtMillis, bodySha256. Any C0 control\n * character in any field is a hard refusal (the separator would otherwise be\n * ambiguous), never something to escape. The proof is an ECDSA P-256 signature\n * with SHA-256 over the canonical input's UTF-8 bytes, wire-encoded as the raw\n * `r ‖ s` 64 bytes in base16-lower (128 hex characters). DER is never accepted\n * on the wire: a platform signer that emits DER (Java `Signature`) converts to\n * raw before sending. Low-S normalization is not required — uniqueness is owned\n * by the nonce and replay window, so signature malleability cannot replay.\n *\n * @module server/client-proof/proof\n */\nimport { createHash, createPrivateKey, createPublicKey, sign, verify, type KeyObject } from 'node:crypto';\n\n/** The only auth profile this module implements. */\nexport const CLIENT_PROOF_PROFILE = 'clientProofV1';\n\n/** `bodySha256` when an operation carries no body: 64 zero characters. */\nexport const ABSENT_BODY_SHA256 = '0'.repeat(64);\n\n/** The contract's `clientProofV1.replayWindowMillis`. */\nexport const DEFAULT_REPLAY_WINDOW_MILLIS = 300_000;\n\n/** The eight proof-input fields, in the order the signature is taken over. */\nexport const PROOF_INPUT_FIELDS = [\n 'profile',\n 'method',\n 'path',\n 'clientId',\n 'keyId',\n 'nonce',\n 'issuedAtMillis',\n 'bodySha256',\n] as const;\n\n/** What joins the proof-input fields. */\nexport const PROOF_INPUT_SEPARATOR = '\\n';\n\n/** Raw `r ‖ s`: two 32-byte big-endian integers, always exactly this long. */\nexport const PROOF_SIGNATURE_BYTES = 64;\n\n/** The wire form is base16-lower of the raw signature: 128 hex characters. */\nexport const PROOF_SIGNATURE_HEX_LENGTH = PROOF_SIGNATURE_BYTES * 2;\n\n/**\n * Exactly 128 lowercase hex characters — anything else (DER, uppercase,\n * truncated, padded) is not a proof this contract describes.\n */\nconst PROOF_SIGNATURE_PATTERN = /^[0-9a-f]{128}$/;\n\n/**\n * Node's name for the fixed-width raw `r ‖ s` signature encoding. Both signing\n * and verification pin it, so the r/s padding rules (a 32-byte length is\n * guaranteed, a would-be 33-byte DER integer is trimmed) live inside\n * node:crypto rather than in a hand-rolled DER converter.\n */\nconst RAW_SIGNATURE_ENCODING = 'ieee-p1363';\n\ntype ProofInputField = (typeof PROOF_INPUT_FIELDS)[number];\n\nexport interface ClientProofInput\n{\n method: string;\n path: string;\n clientId: string;\n keyId: string;\n nonce: string;\n issuedAtMillis: bigint;\n bodySha256: string;\n}\n\n/** A C0 control character appeared in a proof field. */\nexport class ProofInputError extends Error\n{\n constructor()\n {\n super('proof input field contains a C0 control character');\n this.name = 'ProofInputError';\n }\n}\n\n/**\n * The canonical proof-input string the signature is taken over.\n *\n * @throws ProofInputError when any field contains a C0 control character.\n */\nexport function canonicalProofInput(input: ClientProofInput): string\n{\n const values: Record<ProofInputField, string> = {\n profile: CLIENT_PROOF_PROFILE,\n method: input.method,\n path: input.path,\n clientId: input.clientId,\n keyId: input.keyId,\n nonce: input.nonce,\n issuedAtMillis: input.issuedAtMillis.toString(),\n bodySha256: input.bodySha256,\n };\n const fields = PROOF_INPUT_FIELDS.map((name) => values[name]);\n for (const field of fields)\n {\n for (const ch of field)\n {\n if (ch.codePointAt(0)! < 0x20)\n {\n throw new ProofInputError();\n }\n }\n }\n\n return fields.join(PROOF_INPUT_SEPARATOR);\n}\n\n/**\n * The contract's public-key representation — SPKI DER, base64 (the same\n * representation `user_public_keys` and the web ES256 path store) — as a key\n * object. Anything that is not a P-256 EC key is refused at parse time, so a\n * key that could never verify a proof is never registered.\n *\n * @throws when the input is not base64 SPKI DER naming a P-256 key.\n */\nexport function parseClientProofPublicKey(spkiDerBase64: string): KeyObject\n{\n const key = createPublicKey({\n key: Buffer.from(spkiDerBase64, 'base64'),\n format: 'der',\n type: 'spki',\n });\n if (key.asymmetricKeyType !== 'ec' || key.asymmetricKeyDetails?.namedCurve !== 'prime256v1')\n {\n throw new Error('a clientProofV1 public key must be an ECDSA P-256 key');\n }\n\n return key;\n}\n\n/**\n * Verifies a presented proof against `input` and a registered public key.\n *\n * The input is assembled first, so a C0 control character throws no matter\n * what was presented — an unassemblable input is a contract violation, never\n * a proof answer. Then the wire-format gate: a value that is not exactly 128\n * lowercase hex characters — a DER signature, a truncated one, uppercase hex —\n * is invalid before any cryptography happens.\n *\n * @throws ProofInputError when an input field contains a C0 control character.\n */\nexport function verifyClientProof(input: ClientProofInput, presentedProof: string, publicKey: KeyObject): boolean\n{\n const data = Buffer.from(canonicalProofInput(input), 'utf8');\n if (!PROOF_SIGNATURE_PATTERN.test(presentedProof))\n {\n return false;\n }\n\n return verify(\n 'sha256',\n data,\n { key: publicKey, dsaEncoding: RAW_SIGNATURE_ENCODING },\n Buffer.from(presentedProof, 'hex'),\n );\n}\n\n/**\n * Signs `input` with a PKCS#8 DER base64 private key, producing the wire form\n * (raw `r ‖ s`, base16-lower).\n *\n * The verifying half's counterpart, here for tests and dev clients — a\n * production signer lives in the mobile SDKs against hardware-held keys.\n */\nexport function signClientProof(input: ClientProofInput, privateKeyPkcs8DerBase64: string): string\n{\n const key = createPrivateKey({\n key: Buffer.from(privateKeyPkcs8DerBase64, 'base64'),\n format: 'der',\n type: 'pkcs8',\n });\n\n return sign(\n 'sha256',\n Buffer.from(canonicalProofInput(input), 'utf8'),\n { key, dsaEncoding: RAW_SIGNATURE_ENCODING },\n ).toString('hex');\n}\n\n/** Lowercase base16 SHA-256 of `bytes`. */\nexport function sha256Hex(bytes: Uint8Array): string\n{\n return createHash('sha256').update(bytes).digest('hex');\n}\n","/**\n * Every way a clientProofV1 server refuses a request.\n *\n * The contract declares six error codes and forbids inventing a seventh, so\n * every refusal here is one of the six. Two rules decide which code a refusal\n * gets (mirroring the spfn-mobile reference server, the executable spec):\n *\n * 1. A refusal a new session could clear is an auth-family code (401). The SDK\n * re-handshakes exactly once on those.\n * 2. Everything else — the request is not the shape the contract describes —\n * is CONTRACT_UNSUPPORTED: the two ends do not agree on what the contract\n * is. PROOF_INVALID would provoke a pointless re-handshake and\n * PROFILE_REJECTED names one specific thing (a profile outside the\n * allowlist), used for exactly and only that.\n *\n * Every message is a fixed string: a message assembled from the request would\n * put a nonce, session id or body fragment into an error the client may log.\n *\n * @module server/client-proof/refusal\n */\nimport { randomBytes } from 'node:crypto';\n\nimport { encodeCanonicalJson, type CanonicalObject, type CanonicalValue } from './canonical-json';\n\n/** The six wire codes. The SDKs classify by code, never HTTP status. */\nexport type ClientProofErrorCode =\n | 'PROOF_INVALID'\n | 'PROOF_REPLAYED'\n | 'PROOF_EXPIRED'\n | 'SESSION_REVOKED'\n | 'PROFILE_REJECTED'\n | 'CONTRACT_UNSUPPORTED';\n\n/** The declaration order of the six codes — the contract export emits this order. */\nexport const CLIENT_PROOF_ERROR_CODES: readonly ClientProofErrorCode[] = [\n 'PROOF_INVALID',\n 'PROOF_REPLAYED',\n 'PROOF_EXPIRED',\n 'SESSION_REVOKED',\n 'PROFILE_REJECTED',\n 'CONTRACT_UNSUPPORTED',\n];\n\n/** The status each code answers with. The contract export reads this. */\nexport const HTTP_STATUS: Record<ClientProofErrorCode, number> = {\n PROOF_INVALID: 401,\n PROOF_REPLAYED: 401,\n PROOF_EXPIRED: 401,\n SESSION_REVOKED: 401,\n PROFILE_REJECTED: 400,\n CONTRACT_UNSUPPORTED: 409,\n};\n\n/** 128 random bits as lowercase base16 — request ids and control tokens. */\nexport function newHexId(): string\n{\n return randomBytes(16).toString('hex');\n}\n\nexport class ClientProofRefusal\n{\n constructor(\n readonly code: ClientProofErrorCode,\n readonly message: string,\n ) \n {}\n\n get httpStatus(): number\n {\n return HTTP_STATUS[this.code];\n }\n\n /** The canonical bytes of `{\"error\":{\"code\":…,\"message\":…,\"requestId\":…}}`. */\n envelopeBytes(requestId: string): Uint8Array\n {\n const error: CanonicalObject = new Map<string, CanonicalValue>([\n ['code', this.code],\n ['message', this.message],\n ['requestId', requestId],\n ]);\n\n return encodeCanonicalJson(new Map<string, CanonicalValue>([['error', error]]));\n }\n\n /** Nothing request-derived reaches a log through this. */\n toString(): string\n {\n return `ClientProofRefusal(${this.code})`;\n }\n\n // ---- shape: what arrived is not the contract (rule 2) -------------------\n\n static unroutable(): ClientProofRefusal\n {\n return contractViolation('no operation in this contract answers that method and path');\n }\n\n static malformedHeaders(): ClientProofRefusal\n {\n return contractViolation('the request does not carry the contract header fields exactly once each');\n }\n\n static missingContentType(): ClientProofRefusal\n {\n return contractViolation('a request that carries a body must declare the contract content type');\n }\n\n static bodyTooLarge(): ClientProofRefusal\n {\n return contractViolation('the request body exceeds the size this server accepts');\n }\n\n /**\n * The body parsed but its bytes are not the canonical form of what it\n * parsed to. Not PROOF_INVALID even though it is discovered next to the\n * proof: the proof over these bytes verifies perfectly well, and an\n * auth-family answer would tell the client to re-handshake and send the\n * same non-canonical bytes again.\n */\n static bodyNotCanonical(): ClientProofRefusal\n {\n return contractViolation('the request body is not the canonical JSON form of the value it encodes');\n }\n\n static bodyNotTheDeclaredType(): ClientProofRefusal\n {\n return contractViolation('the request body is not the request type this operation declares');\n }\n\n static sessionHeaderMisplaced(): ClientProofRefusal\n {\n return contractViolation('the session header is present exactly on the operations that require one');\n }\n\n static unprocessable(): ClientProofRefusal\n {\n return contractViolation('the request could not be processed');\n }\n\n /**\n * A client that ships separately from the server said nothing about which\n * contract it was built against. Without it the server cannot tell whether\n * the two ends agree, and answering as though they do is what produces the\n * undecodable body this check exists to replace.\n */\n static contractVersionMissing(): ClientProofRefusal\n {\n return contractViolation('a client of this kind must state the contract version it was generated from');\n }\n\n static contractVersionUnsupported(): ClientProofRefusal\n {\n return contractViolation('the stated contract version is outside the range this server serves');\n }\n\n // ---- the profile allowlist ----------------------------------------------\n\n static profileRejected(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROFILE_REJECTED', \"the named auth profile is not on this contract's allowlist\");\n }\n\n /**\n * A request that names a profile and presents Bearer credentials as well.\n * The profile named is a real one, so this is not a shape the two ends\n * disagree about: the request asked to be authenticated two ways at once\n * and the profile it named is the one refused.\n */\n static credentialsMixed(): ClientProofRefusal\n {\n return new ClientProofRefusal(\n 'PROFILE_REJECTED',\n 'an auth profile and Bearer credentials must not be mixed in one request',\n );\n }\n\n // ---- auth: a new session might clear it (rule 1) -------------------------\n\n static sessionRevoked(): ClientProofRefusal\n {\n return new ClientProofRefusal('SESSION_REVOKED', 'the key or session was revoked');\n }\n\n static proofExpired(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROOF_EXPIRED', 'issuedAtMillis falls outside the replay window');\n }\n\n static proofReplayed(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROOF_REPLAYED', 'the nonce was already used inside the replay window');\n }\n\n static proofInvalid(): ClientProofRefusal\n {\n return new ClientProofRefusal('PROOF_INVALID', 'the client proof did not verify');\n }\n}\n\nfunction contractViolation(message: string): ClientProofRefusal\n{\n return new ClientProofRefusal('CONTRACT_UNSUPPORTED', message);\n}\n","/**\n * The clientProofV1 replay ledger as a pluggable store — the same pattern\n * one-time-token uses: an in-memory default, an opt-in Redis/Valkey store on\n * top of `getCache()`, and a module-level configuration hook.\n *\n * One data structure owns the memory semantics: `MemoryReplayLedger` is used\n * synchronously by the dev surface's `ClientProofState` (whose `admit` must\n * stay synchronous to keep its single-thread atomicity argument) and wrapped\n * by `MemoryReplayStore` for the async middleware path. There is exactly one\n * implementation of \"spent inside the window\", not two.\n *\n * The nonce-spending rule is the contract's: a nonce is recorded only when a\n * request is admitted, so `isSpent` (the replay-order check) and `spend` (the\n * post-verification record) are separate calls. `spend` is check-and-set — it\n * answers false when another request spent the nonce between the two calls —\n * so the race two concurrent same-nonce requests can open is closed at the\n * store, for memory and Redis alike.\n *\n * Store failure is the caller's refusal, never a pass-through: the middleware\n * path treats a throwing store as \"reject the request\" (fail-closed). An auth\n * surface does not fail open.\n *\n * @module server/client-proof/replay-store\n */\nimport { getCache } from '@spfn/core/cache';\n\nimport { DEFAULT_REPLAY_WINDOW_MILLIS, sha256Hex } from './proof';\n\n/**\n * The ledger key. `JSON.stringify` of the pair, so no crafted clientId/nonce\n * concatenation can collide with another pair — the fields are checked for C0\n * controls only later, at proof verification, so the key must be unambiguous\n * for arbitrary strings.\n */\nexport function replayLedgerKey(clientId: string, nonce: string): string\n{\n return JSON.stringify([clientId, nonce]);\n}\n\n/**\n * What the middleware's replay ledger must answer. Both methods may reject;\n * the caller refuses the request when they do (fail-closed).\n */\nexport interface ClientProofReplayStore\n{\n /** True when (clientId, nonce) was already spent inside the window. */\n isSpent(clientId: string, nonce: string): Promise<boolean>;\n\n /**\n * Records the pair as spent. False when it was already spent — the caller\n * lost a race and must answer PROOF_REPLAYED, not accept twice.\n */\n spend(clientId: string, nonce: string): Promise<boolean>;\n}\n\n/**\n * The in-memory ledger — the single implementation of the window semantics.\n *\n * Entries carry the millisecond they were recorded at; `prune` drops an entry\n * only once a proof carrying that timestamp would be refused as expired\n * anyway (the exact negation of the admission window check). All methods are\n * synchronous so `ClientProofState.admit` can stay atomic on Node's single\n * thread.\n */\nexport class MemoryReplayLedger\n{\n /** replayLedgerKey(...) → the millis it was spent at. */\n private readonly spent = new Map<string, number>();\n\n isSpent(clientId: string, nonce: string): boolean\n {\n return this.spent.has(replayLedgerKey(clientId, nonce));\n }\n\n /** Records the pair at `atMillis`; false when it was already spent. */\n spend(clientId: string, nonce: string, atMillis: number): boolean\n {\n const key = replayLedgerKey(clientId, nonce);\n if (this.spent.has(key))\n {\n return false;\n }\n this.spent.set(key, atMillis);\n\n return true;\n }\n\n /** Drops entries older than the window, judged against `nowMillis`. */\n prune(nowMillis: number, windowMillis: number): void\n {\n for (const [key, spentAtMillis] of this.spent)\n {\n if (nowMillis - spentAtMillis > windowMillis)\n {\n this.spent.delete(key);\n }\n }\n }\n\n get size(): number\n {\n return this.spent.size;\n }\n\n clear(): void\n {\n this.spent.clear();\n }\n}\n\n/**\n * The default store: a process-local `MemoryReplayLedger` on the wall clock.\n *\n * Correct for a single process. Behind a multi-instance deployment each\n * instance keeps its own ledger, so a replay against a *different* instance\n * is not seen — that deployment opts into `RedisReplayStore`.\n */\nexport class MemoryReplayStore implements ClientProofReplayStore\n{\n private readonly ledger = new MemoryReplayLedger();\n\n constructor(private readonly windowMillis: number = DEFAULT_REPLAY_WINDOW_MILLIS)\n {}\n\n async isSpent(clientId: string, nonce: string): Promise<boolean>\n {\n this.ledger.prune(Date.now(), this.windowMillis);\n\n return this.ledger.isSpent(clientId, nonce);\n }\n\n async spend(clientId: string, nonce: string): Promise<boolean>\n {\n const now = Date.now();\n this.ledger.prune(now, this.windowMillis);\n\n return this.ledger.spend(clientId, nonce, now);\n }\n}\n\n/**\n * The opt-in shared ledger over `getCache()` (ioredis): `SET NX PX <window>`.\n *\n * The key hashes the pair, so arbitrary clientId/nonce strings become short,\n * safe Redis keys with no ambiguity. `PX` makes Redis expire the entry itself\n * exactly when a proof reusing the nonce would pass the window check again.\n *\n * Fail-closed by construction: when the cache is not configured or a command\n * rejects, the error propagates and the caller refuses the request. Nothing\n * here answers \"not spent\" on a store it could not reach.\n */\nexport class RedisReplayStore implements ClientProofReplayStore\n{\n constructor(private readonly windowMillis: number = DEFAULT_REPLAY_WINDOW_MILLIS)\n {}\n\n async isSpent(clientId: string, nonce: string): Promise<boolean>\n {\n return await this.cache().exists(this.key(clientId, nonce)) === 1;\n }\n\n async spend(clientId: string, nonce: string): Promise<boolean>\n {\n return await this.cache().set(this.key(clientId, nonce), '1', 'PX', this.windowMillis, 'NX') === 'OK';\n }\n\n private cache(): NonNullable<ReturnType<typeof getCache>>\n {\n const cache = getCache();\n if (!cache)\n {\n throw new Error('client-proof replay ledger: cache is not available');\n }\n\n return cache;\n }\n\n private key(clientId: string, nonce: string): string\n {\n return `spfn:auth:client-proof:replay:${sha256Hex(Buffer.from(replayLedgerKey(clientId, nonce), 'utf8'))}`;\n }\n}\n\n// ---- module-level configuration (the one-time-token pattern) ---------------\n\nlet configured: ClientProofReplayStore | null = null;\n\n/**\n * Installs the replay store the authenticate middleware uses. Pass\n * `new RedisReplayStore()` to opt into the shared ledger; pass null to return\n * to the in-memory default.\n */\nexport function configureClientProofReplayStore(store: ClientProofReplayStore | null): void\n{\n configured = store;\n}\n\n/** The configured store, or a lazily created in-memory default. */\nexport function getClientProofReplayStore(): ClientProofReplayStore\n{\n configured ??= new MemoryReplayStore();\n\n return configured;\n}\n","/**\n * Everything a clientProofV1 server remembers between requests: issued\n * sessions, the replay ledger, revoked keys and the key directory.\n *\n * The admission order is the contract's, not this file's invention\n * (`clientProofV1.revocationRule` + the replay fixtures):\n *\n * 1. revoked keyId / invalid session → SESSION_REVOKED — before proof\n * verification, so revocation stays distinguishable from a bad proof;\n * 2. issuedAtMillis outside the replay window (0 <= age <= window) → PROOF_EXPIRED;\n * 3. a repeated (clientId, nonce) pair inside the window → PROOF_REPLAYED;\n * 4. only then signature verification → PROOF_INVALID when it does not verify.\n *\n * A nonce is recorded as spent only on admission: a request refused for any\n * earlier reason has not spent anything, so a client that fixes the reason and\n * retries with the same nonce is not punished twice for one mistake. This is\n * why core's `NonceStore.checkAndSet` (which records on check) is not reused\n * here — its semantics would spend a nonce on a refused request.\n *\n * `admit` is synchronous, so on Node's single thread the whole sequence is\n * atomic: two requests presenting the same nonce cannot interleave inside it.\n *\n * @module server/client-proof/state\n */\nimport type { KeyObject } from 'node:crypto';\n\nimport {\n DEFAULT_REPLAY_WINDOW_MILLIS,\n parseClientProofPublicKey,\n verifyClientProof,\n type ClientProofInput,\n} from './proof';\nimport { ClientProofRefusal, newHexId } from './refusal';\nimport { MemoryReplayLedger } from './replay-store';\n\n/** Millisecond clock. Injectable so expiry paths are testable without waiting. */\nexport interface ClientProofClock\n{\n nowMillis(): number;\n}\n\nexport function systemClock(): ClientProofClock\n{\n return { nowMillis: () => Date.now() };\n}\n\n/** A clock a test (or the dev control surface) can move forward. */\nexport class TestClock implements ClientProofClock\n{\n constructor(private millis: number) \n {}\n\n nowMillis(): number\n {\n return this.millis;\n }\n\n advance(byMillis: number): void\n {\n this.millis += byMillis;\n }\n}\n\n/** What `stats()` reports. Counters only; nothing a request carried. */\nexport interface ClientProofStats\n{\n requestCount: number;\n handshakeCount: number;\n echoCount: number;\n itemsListCount: number;\n refusalCount: number;\n liveSessionCount: number;\n spentNonceCount: number;\n}\n\ninterface ClientProofSession\n{\n clientId: string;\n keyId: string;\n expiresAtMillis: number;\n}\n\ninterface PathHold\n{\n millis: number;\n remaining: number;\n}\n\nexport interface ClientProofStateOptions\n{\n /**\n * keyId → registered public key, as SPKI DER base64. The private half\n * never reaches the server: a client generates its keypair (hardware-held\n * on mobile) and only the public key is registered — at construction here,\n * or later through `registerPublicKey` (the dev `/control/register-key`\n * route).\n */\n publicKeys: Record<string, string>;\n\n clock?: ClientProofClock;\n\n /** @default 600000 */\n sessionTtlMillis?: number;\n\n /** The contract's replay window. @default 300000 */\n replayWindowMillis?: number;\n}\n\nexport const DEFAULT_SESSION_TTL_MILLIS = 600_000;\n\nexport class ClientProofState\n{\n readonly replayWindowMillis: number;\n\n private readonly clock: ClientProofClock;\n private readonly initialPublicKeys: ReadonlyMap<string, KeyObject>;\n private readonly publicKeys = new Map<string, KeyObject>();\n private readonly sessions = new Map<string, ClientProofSession>();\n\n /** The replay ledger — the shared memory implementation, used dev-only here. */\n private readonly spentNonces = new MemoryReplayLedger();\n\n private readonly revokedKeyIds = new Set<string>();\n private readonly holds = new Map<string, PathHold>();\n\n private readonly initialSessionTtlMillis: number;\n private sessionTtlMillis: number;\n\n private requestCount = 0;\n private handshakeCount = 0;\n private echoCount = 0;\n private itemsListCount = 0;\n private refusalCount = 0;\n\n constructor(options: ClientProofStateOptions)\n {\n this.clock = options.clock ?? systemClock();\n this.initialSessionTtlMillis = options.sessionTtlMillis ?? DEFAULT_SESSION_TTL_MILLIS;\n this.sessionTtlMillis = this.initialSessionTtlMillis;\n this.replayWindowMillis = options.replayWindowMillis ?? DEFAULT_REPLAY_WINDOW_MILLIS;\n // Parsed once here, so a key that is not P-256 SPKI fails loudly at\n // construction rather than as a PROOF_INVALID mystery at request time.\n this.initialPublicKeys = new Map(\n Object.entries(options.publicKeys).map(([keyId, spki]) => [keyId, parseClientProofPublicKey(spki)]),\n );\n for (const [keyId, key] of this.initialPublicKeys)\n {\n this.publicKeys.set(keyId, key);\n }\n }\n\n // ---- key registration --------------------------------------------------\n\n /**\n * Registers (or replaces) the public key `keyId` presents proofs under.\n *\n * @throws when the key is not base64 SPKI DER naming a P-256 key.\n */\n registerPublicKey(keyId: string, publicKeySpkiDerBase64: string): void\n {\n this.publicKeys.set(keyId, parseClientProofPublicKey(publicKeySpkiDerBase64));\n }\n\n // ---- admission ---------------------------------------------------------\n\n /**\n * Runs the contract's checks in the contract's order and returns the\n * refusal, or null when the request is admitted (spending its nonce).\n */\n admit(args: {\n clientId: string;\n keyId: string;\n presentedSessionId: string | null;\n requiresSession: boolean;\n proofInput: ClientProofInput;\n presentedProof: string;\n }): ClientProofRefusal | null\n {\n const now = this.clock.nowMillis();\n this.prune(now);\n\n // 1. Revocation, before anything the proof could explain. A revoked key\n // and a dropped session are the same answer on purpose: both are\n // cleared by opening a new session.\n if (this.revokedKeyIds.has(args.keyId))\n {\n return ClientProofRefusal.sessionRevoked();\n }\n if (args.requiresSession)\n {\n const session = args.presentedSessionId === null ? undefined : this.sessions.get(args.presentedSessionId);\n if (session === undefined || session.expiresAtMillis <= now\n || session.keyId !== args.keyId || session.clientId !== args.clientId)\n {\n return ClientProofRefusal.sessionRevoked();\n }\n }\n\n // 2. The replay window, judged against this server's clock.\n const age = now - Number(args.proofInput.issuedAtMillis);\n if (age < 0 || age > this.replayWindowMillis)\n {\n return ClientProofRefusal.proofExpired();\n }\n\n // 3. One acceptance per (clientId, nonce) inside that window.\n if (this.spentNonces.isSpent(args.clientId, args.proofInput.nonce))\n {\n return ClientProofRefusal.proofReplayed();\n }\n\n // 4. The proof itself, last, so the three answers above stay\n // distinguishable. An unregistered keyId lands here rather than in\n // step 1, and shares PROOF_INVALID with a failed signature: it was\n // never registered, so it was never revoked, there is nothing for a\n // new session to fix, and whether a keyId exists is not inferable\n // from the refusal — the same non-disclosure the revocation rule\n // keeps.\n const publicKey = this.publicKeys.get(args.keyId);\n if (publicKey === undefined)\n {\n return ClientProofRefusal.proofInvalid();\n }\n if (!verifyClientProof(args.proofInput, args.presentedProof, publicKey))\n {\n return ClientProofRefusal.proofInvalid();\n }\n\n this.spentNonces.spend(args.clientId, args.proofInput.nonce, Number(args.proofInput.issuedAtMillis));\n\n return null;\n }\n\n // ---- sessions ----------------------------------------------------------\n\n /** Opens a session and returns its id and the expiry the server advertises. */\n openSession(clientId: string, keyId: string): { sessionId: string; expiresAtMillis: number }\n {\n const now = this.clock.nowMillis();\n this.prune(now);\n const sessionId = newHexId();\n const expiresAtMillis = now + this.sessionTtlMillis;\n this.sessions.set(sessionId, { clientId, keyId, expiresAtMillis });\n\n return { sessionId, expiresAtMillis };\n }\n\n /** Test hook: installs a session with a chosen id (wire-fixture replays). */\n seedSession(sessionId: string, clientId: string, keyId: string, expiresAtMillis: number): void\n {\n this.sessions.set(sessionId, { clientId, keyId, expiresAtMillis });\n }\n\n /** Drops every session, as a restart would. Advertised expiries stay told. */\n expireSessions(): void\n {\n this.sessions.clear();\n }\n\n /** Revokes a key and drops the sessions it opened. */\n revokeKey(keyId: string): void\n {\n this.revokedKeyIds.add(keyId);\n for (const [sessionId, session] of this.sessions)\n {\n if (session.keyId === keyId)\n {\n this.sessions.delete(sessionId);\n }\n }\n }\n\n setSessionTtlMillis(millis: number): void\n {\n this.sessionTtlMillis = millis;\n }\n\n /** Returns the state to how it started, counters and registered keys included. */\n reset(): void\n {\n this.publicKeys.clear();\n for (const [keyId, key] of this.initialPublicKeys)\n {\n this.publicKeys.set(keyId, key);\n }\n this.sessions.clear();\n this.spentNonces.clear();\n this.revokedKeyIds.clear();\n this.holds.clear();\n this.sessionTtlMillis = this.initialSessionTtlMillis;\n this.requestCount = 0;\n this.handshakeCount = 0;\n this.echoCount = 0;\n this.itemsListCount = 0;\n this.refusalCount = 0;\n }\n\n // ---- delays (dev/test only) --------------------------------------------\n\n /** Makes the next `count` requests to `path` wait `millis` before processing. */\n holdPath(path: string, millis: number, count: number): void\n {\n this.holds.set(path, { millis, remaining: count });\n }\n\n /** Consumes one configured delay for `path`; returns how long to wait, or 0. */\n takeHoldMillis(path: string): number\n {\n const hold = this.holds.get(path);\n if (hold === undefined)\n {\n return 0;\n }\n hold.remaining -= 1;\n if (hold.remaining <= 0)\n {\n this.holds.delete(path);\n }\n\n return hold.millis;\n }\n\n // ---- counters ----------------------------------------------------------\n\n recordRequest(): void\n {\n this.requestCount += 1;\n }\n\n recordOperation(operationId: string): void\n {\n if (operationId === 'auth.clientProof.handshake')\n {\n this.handshakeCount += 1;\n }\n else if (operationId === 'echo.send')\n {\n this.echoCount += 1;\n }\n else if (operationId === 'items.list')\n {\n this.itemsListCount += 1;\n }\n }\n\n recordRefusal(): void\n {\n this.refusalCount += 1;\n }\n\n stats(): ClientProofStats\n {\n this.prune(this.clock.nowMillis());\n\n return {\n requestCount: this.requestCount,\n handshakeCount: this.handshakeCount,\n echoCount: this.echoCount,\n itemsListCount: this.itemsListCount,\n refusalCount: this.refusalCount,\n liveSessionCount: this.sessions.size,\n spentNonceCount: this.spentNonces.size,\n };\n }\n\n nowMillis(): number\n {\n return this.clock.nowMillis();\n }\n\n /** The clock, exposed for the dev control surface's advance-clock route. */\n get clockRef(): ClientProofClock\n {\n return this.clock;\n }\n\n // ---- housekeeping ------------------------------------------------------\n\n /**\n * Drops what can no longer affect an answer. The nonce predicate is the\n * exact negation of the window check in `admit`: an entry is dropped only\n * once a proof carrying that issuedAtMillis would be refused as expired\n * anyway. Dropping one moment earlier would let a nonce inside the window\n * be spent twice.\n */\n private prune(nowMillis: number): void\n {\n for (const [sessionId, session] of this.sessions)\n {\n if (session.expiresAtMillis <= nowMillis)\n {\n this.sessions.delete(sessionId);\n }\n }\n this.spentNonces.prune(nowMillis, this.replayWindowMillis);\n }\n}\n","/**\n * The checks between a clientProofV1 request arriving and being applied.\n *\n * Shape first, then the profile allowlist, then the proof. That order is\n * forced: none of the proof checks can run until the fields they read are\n * known to be present and the body is known to be the bytes the digest is\n * supposed to cover. The order *inside* the proof checks is the contract's and\n * lives in `ClientProofState.admit`.\n *\n * @module server/client-proof/admission\n */\nimport { isCanonicalBytes, parseCanonicalJson, type CanonicalValue } from './canonical-json';\nimport { CLIENT_PROOF_PROFILE, sha256Hex, type ClientProofInput } from './proof';\nimport { ClientProofRefusal } from './refusal';\nimport type { ClientProofState } from './state';\n\n/** D23 wire-header names, ratified as proposed by the mobile dev bundle. */\nexport const CLIENT_PROOF_HEADERS = {\n profile: 'x-spfn-auth-profile',\n clientId: 'x-spfn-client-id',\n keyId: 'x-spfn-key-id',\n nonce: 'x-spfn-nonce',\n issuedAtMillis: 'x-spfn-issued-at',\n proof: 'x-spfn-proof',\n session: 'x-spfn-session',\n} as const;\n\nexport const CLIENT_PROOF_CONTENT_TYPE = 'application/json';\n\nconst INT64_MIN = -(2n ** 63n);\nconst INT64_MAX = 2n ** 63n - 1n;\n\n/** The contract header fields one request presented. */\nexport interface ClientProofCredentials\n{\n profile: string;\n clientId: string;\n keyId: string;\n nonce: string;\n issuedAtMillis: bigint;\n proof: string;\n sessionId: string | null;\n}\n\nexport type Admission =\n | { admitted: false; refusal: ClientProofRefusal }\n | { admitted: true; value: CanonicalValue; credentials: ClientProofCredentials };\n\n/**\n * Runs every check for one operation over already-read body bytes.\n *\n * `path` must be the operation's contract path (what the client signed), not a\n * proxied or rewritten one.\n */\nexport function admitClientProofRequest(args: {\n state: ClientProofState;\n headers: Headers;\n method: string;\n path: string;\n requiresSession: boolean;\n body: Uint8Array;\n}): Admission\n{\n const credentials = readCredentials(args.headers);\n if (credentials === null)\n {\n return refused(ClientProofRefusal.malformedHeaders());\n }\n if (credentials.profile !== CLIENT_PROOF_PROFILE)\n {\n return refused(ClientProofRefusal.profileRejected());\n }\n if (!isRequestContentType(args.headers.get('content-type')))\n {\n return refused(ClientProofRefusal.missingContentType());\n }\n if (args.requiresSession !== (credentials.sessionId !== null))\n {\n return refused(ClientProofRefusal.sessionHeaderMisplaced());\n }\n\n let value: CanonicalValue;\n try\n {\n value = parseCanonicalJson(args.body);\n }\n catch\n {\n return refused(ClientProofRefusal.bodyNotCanonical());\n }\n // The proof binds the received bytes; accepting a re-serialization would\n // let two implementations disagree about what was signed.\n if (!isCanonicalBytes(args.body, value))\n {\n return refused(ClientProofRefusal.bodyNotCanonical());\n }\n\n const proofInput: ClientProofInput = {\n method: args.method,\n path: args.path,\n clientId: credentials.clientId,\n keyId: credentials.keyId,\n nonce: credentials.nonce,\n issuedAtMillis: credentials.issuedAtMillis,\n bodySha256: sha256Hex(args.body),\n };\n\n let refusal: ClientProofRefusal | null;\n try\n {\n refusal = args.state.admit({\n clientId: credentials.clientId,\n keyId: credentials.keyId,\n presentedSessionId: credentials.sessionId,\n requiresSession: args.requiresSession,\n proofInput,\n presentedProof: credentials.proof,\n });\n }\n catch\n {\n // A C0 control character in a header field makes the proof input\n // unassemblable — the request is not the shape the contract describes.\n return refused(ClientProofRefusal.unprocessable());\n }\n if (refusal !== null)\n {\n return refused(refusal);\n }\n\n return { admitted: true, value, credentials };\n}\n\nfunction refused(refusal: ClientProofRefusal): Admission\n{\n return { admitted: false, refusal };\n}\n\n/**\n * The contract header fields, or null when any is absent or malformed.\n *\n * Fetch `Headers` folds a repeated field into one comma-joined value, so\n * \"sent more than once\" is not directly observable here; a folded value fails\n * either the issuedAt grammar or proof verification instead.\n *\n * Exported for the authenticate middleware's profile path, which runs the\n * same shape checks over arbitrary routes.\n */\nexport function readCredentials(headers: Headers): ClientProofCredentials | null\n{\n const profile = headers.get(CLIENT_PROOF_HEADERS.profile);\n const clientId = headers.get(CLIENT_PROOF_HEADERS.clientId);\n const keyId = headers.get(CLIENT_PROOF_HEADERS.keyId);\n const nonce = headers.get(CLIENT_PROOF_HEADERS.nonce);\n const issuedAtRaw = headers.get(CLIENT_PROOF_HEADERS.issuedAtMillis);\n const proof = headers.get(CLIENT_PROOF_HEADERS.proof);\n if (profile === null || clientId === null || keyId === null\n || nonce === null || issuedAtRaw === null || proof === null)\n {\n return null;\n }\n const issuedAtMillis = parseInt64(issuedAtRaw);\n if (issuedAtMillis === null)\n {\n return null;\n }\n\n return {\n profile,\n clientId,\n keyId,\n nonce,\n issuedAtMillis,\n proof,\n sessionId: headers.get(CLIENT_PROOF_HEADERS.session),\n };\n}\n\nfunction parseInt64(raw: string): bigint | null\n{\n if (!/^[+-]?\\d{1,19}$/.test(raw))\n {\n return null;\n }\n const value = BigInt(raw);\n if (value < INT64_MIN || value > INT64_MAX)\n {\n return null;\n }\n\n return value;\n}\n\n/** Exported for the authenticate middleware's profile path. */\nexport function isRequestContentType(value: string | null): boolean\n{\n if (value === null)\n {\n return false;\n }\n\n return value.split(';')[0].trim().toLowerCase() === CLIENT_PROOF_CONTENT_TYPE;\n}\n","/**\n * The mobile dev-contract types and operations, decoded from / encoded to\n * canonical values. Strict on purpose: a missing required field, a wrong type\n * or an unknown field is \"not the request type this operation declares\".\n *\n * This module is the source of truth for `operations`. The exported contract\n * bundle (`contracts/mobile/spfn-mobile-contract.json`) is generated from it\n * by `contract-bundle.ts`; spfn-mobile consumes that export rather than the\n * other way round.\n *\n * @module server/client-proof/contract-types\n */\nimport {\n CORE_TIME_OPERATION_ID,\n CORE_TIME_ROUTE,\n} from '@spfn/core/server';\n\nimport type { CanonicalObject, CanonicalValue } from './canonical-json';\n\nexport interface ContractOperation\n{\n id:\n | 'auth.clientProof.handshake'\n | 'echo.send'\n | 'items.list'\n | 'auth.enroll.register'\n | 'auth.enroll.login'\n | 'auth.enroll.oauthNative'\n | 'auth.keys.rotate'\n | 'auth.keys.list'\n | 'auth.keys.revoke'\n | 'auth.keys.revokeAll'\n | typeof CORE_TIME_OPERATION_ID;\n method: 'GET' | 'POST';\n path: string;\n\n /**\n * How a call is admitted. `clientProofV1` operations run the proof\n * admission order; `none` operations are the unproven class — accepted\n * with neither proof headers nor a session header, because enrollment is\n * called before any key exists to sign with.\n */\n authProfile: 'clientProofV1' | 'none';\n requiresSession: boolean;\n /** Absent only when the operation has no request body. */\n requestType?: string;\n responseType: string;\n summary: string;\n\n /**\n * The contract version this operation first appeared in. Required, so an\n * operation added later cannot ship without one: omitting it is a compile\n * error rather than a hole a consumer discovers.\n *\n * It is history, not policy. This contract's compatibility policy is\n * `allOrNothing` — one version passes or refuses the whole surface — so\n * nothing here changes a verdict. It exists so a deprecation has somewhere\n * to be recorded, and as the precedent an app contract's `perOperation`\n * policy reads.\n */\n since: string;\n\n /**\n * The contract version that marked this operation deprecated, if one has.\n * A deprecated operation is still served: the mark is the notice that opens\n * the grace period before removal.\n */\n deprecatedIn?: string;\n\n /**\n * The contract version that removed this operation, if one has.\n *\n * A removed operation leaves this list, so nothing here carries the field\n * today. When the first removal happens, `removedIn` is where the fact is\n * recorded — how a removed operation stays visible after leaving the list\n * is decided then, not invented in advance.\n */\n removedIn?: string;\n}\n\nfunction importCoreTimeContract()\n{\n const { method, path, contract } = CORE_TIME_ROUTE;\n if (method !== 'GET'\n || typeof path !== 'string'\n || contract?.auth !== 'none'\n || contract.requiresSession !== false\n || typeof contract.since !== 'string')\n {\n throw new Error('core.time does not match the clientProofV1 synchronization prerequisite');\n }\n\n return {\n id: CORE_TIME_OPERATION_ID,\n method,\n path,\n authProfile: contract.auth,\n requiresSession: contract.requiresSession,\n sourceSince: contract.since,\n } as const;\n}\n\n/** Validated projection of the imported core route contract. */\nexport const IMPORTED_CORE_TIME_CONTRACT = importCoreTimeContract();\n\n/**\n * The core capability clientProofV1 needs before the client can mint a proof.\n *\n * Transport and admission fields come from core's exported route contract so\n * auth cannot silently restate a different path or policy. `since` is the\n * mobile-contract history, not core's package-contract history.\n */\nexport const CORE_PREREQUISITE_OPERATIONS: readonly ContractOperation[] = [\n {\n id: IMPORTED_CORE_TIME_CONTRACT.id,\n method: IMPORTED_CORE_TIME_CONTRACT.method,\n path: IMPORTED_CORE_TIME_CONTRACT.path,\n authProfile: IMPORTED_CORE_TIME_CONTRACT.authProfile,\n requiresSession: IMPORTED_CORE_TIME_CONTRACT.requiresSession,\n responseType: 'ServerTimeResponse',\n summary: 'Returns the server epoch used to timestamp clientProofV1 proofs.',\n since: '0.9.0',\n },\n];\n\nexport const CONTRACT_OPERATIONS: readonly ContractOperation[] = [\n {\n id: 'auth.clientProof.handshake',\n method: 'POST',\n path: '/v1/auth/client-proof/handshake',\n authProfile: 'clientProofV1',\n requiresSession: false,\n requestType: 'HandshakeRequest',\n responseType: 'HandshakeResponse',\n summary: 'Presents a client proof and opens a session.',\n since: '0.1.0',\n },\n {\n id: 'echo.send',\n method: 'POST',\n path: '/v1/echo',\n authProfile: 'clientProofV1',\n requiresSession: true,\n requestType: 'EchoRequest',\n responseType: 'EchoResponse',\n summary: 'Authenticated round trip used as the smallest real vertical slice.',\n since: '0.1.0',\n },\n {\n id: 'items.list',\n method: 'POST',\n path: '/v1/items/list',\n authProfile: 'clientProofV1',\n requiresSession: true,\n requestType: 'ListItemsRequest',\n responseType: 'ListItemsResponse',\n summary: 'Authenticated paged read covering optional fields and arrays.',\n since: '0.1.0',\n },\n];\n\n/**\n * The `/_auth` surface exported into the mobile contract: enrollment, login\n * and key rotation. These are ordinary SPFN REST routes, not canonical-JSON\n * operations — the dev handler never serves them, and their wire rules are\n * the `restOperations` section of the bundle, not `canonicalJson`.\n *\n * The three `authProfile: 'none'` operations are the unproven class: they are\n * accepted with neither proof headers nor a session header, because they are\n * how a client obtains a key in the first place. `auth.keys.rotate` requires\n * an authenticated caller (a clientProofV1 proof on this surface); an\n * unproven call to it is refused like any failed admission.\n */\nexport const AUTH_SURFACE_OPERATIONS: readonly ContractOperation[] = [\n {\n id: 'auth.enroll.register',\n method: 'POST',\n path: '/_auth/register',\n authProfile: 'none',\n requiresSession: false,\n requestType: 'RegisterRequest',\n responseType: 'RegisterResponse',\n summary: 'Registers an account with a verification token and enrolls the client-generated public key.',\n since: '0.3.0',\n },\n {\n id: 'auth.enroll.login',\n method: 'POST',\n path: '/_auth/login',\n authProfile: 'none',\n requiresSession: false,\n requestType: 'LoginRequest',\n responseType: 'LoginResponse',\n summary: 'Authenticates with password credentials and enrolls a fresh client-generated public key.',\n since: '0.3.0',\n },\n {\n id: 'auth.enroll.oauthNative',\n method: 'POST',\n path: '/_auth/oauth/{provider}/native',\n authProfile: 'none',\n requiresSession: false,\n requestType: 'OauthNativeRequest',\n responseType: 'OauthNativeResponse',\n summary: 'Verifies a native/web social id_token server-side and enrolls the client-generated public key.',\n since: '0.3.0',\n },\n {\n id: 'auth.keys.rotate',\n method: 'POST',\n path: '/_auth/keys/rotate',\n authProfile: 'clientProofV1',\n requiresSession: false,\n requestType: 'RotateKeyRequest',\n responseType: 'RotateKeyResponse',\n summary: 'Replaces the authenticated key with a new client-generated public key before its TTL runs out.',\n since: '0.3.0',\n },\n {\n id: 'auth.keys.list',\n method: 'POST',\n path: '/_auth/keys/list',\n authProfile: 'clientProofV1',\n requiresSession: false,\n requestType: 'ListKeysRequest',\n responseType: 'ListKeysResponse',\n summary: 'Lists the keys registered to the caller, one per device that can sign for them.',\n since: '0.4.1',\n },\n {\n id: 'auth.keys.revoke',\n method: 'POST',\n path: '/_auth/keys/revoke',\n authProfile: 'clientProofV1',\n requiresSession: false,\n requestType: 'RevokeKeyRequest',\n responseType: 'RevokeKeyResponse',\n summary: 'Revokes one of the caller\\'s keys, signing that device out.',\n since: '0.4.1',\n },\n {\n id: 'auth.keys.revokeAll',\n method: 'POST',\n path: '/_auth/keys/revoke-all',\n authProfile: 'clientProofV1',\n requiresSession: false,\n requestType: 'RevokeAllKeysRequest',\n responseType: 'RevokeAllKeysResponse',\n summary: 'Revokes every key the caller has, sparing the calling device unless asked otherwise.',\n since: '0.4.1',\n },\n];\n\n/** The body is canonical JSON but not the declared request type. */\nexport class ContractTypeError extends Error\n{\n constructor()\n {\n super('not the declared contract type');\n this.name = 'ContractTypeError';\n }\n}\n\nexport interface HandshakeRequest\n{\n clientId: string;\n keyId: string;\n nonce: string;\n issuedAtMillis: bigint;\n}\n\nexport interface EchoRequest\n{\n message: string;\n sequence: bigint;\n}\n\nexport interface ListItemsRequest\n{\n limit: bigint;\n cursor?: string;\n}\n\nexport interface ContractItem\n{\n id: string;\n name: string;\n updatedAtMillis: bigint;\n}\n\n// ============================================================================\n// Decoding\n// ============================================================================\n\nexport function decodeHandshakeRequest(value: CanonicalValue): HandshakeRequest\n{\n const members = objectWithKeys(value, ['clientId', 'keyId', 'nonce', 'issuedAtMillis'], []);\n\n return {\n clientId: text(members.get('clientId')),\n keyId: text(members.get('keyId')),\n nonce: text(members.get('nonce')),\n issuedAtMillis: integer(members.get('issuedAtMillis')),\n };\n}\n\nexport function decodeEchoRequest(value: CanonicalValue): EchoRequest\n{\n const members = objectWithKeys(value, ['message', 'sequence'], []);\n\n return {\n message: text(members.get('message')),\n sequence: integer(members.get('sequence')),\n };\n}\n\nexport function decodeListItemsRequest(value: CanonicalValue): ListItemsRequest\n{\n const members = objectWithKeys(value, ['limit'], ['cursor']);\n const request: ListItemsRequest = { limit: integer(members.get('limit')) };\n if (members.has('cursor'))\n {\n request.cursor = text(members.get('cursor'));\n }\n\n return request;\n}\n\nfunction objectWithKeys(\n value: CanonicalValue,\n required: string[],\n optional: string[],\n): CanonicalObject\n{\n if (!(value instanceof Map))\n {\n throw new ContractTypeError();\n }\n for (const key of required)\n {\n if (!value.has(key))\n {\n throw new ContractTypeError();\n }\n }\n for (const key of value.keys())\n {\n if (!required.includes(key) && !optional.includes(key))\n {\n throw new ContractTypeError();\n }\n }\n\n return value;\n}\n\nfunction text(value: CanonicalValue | undefined): string\n{\n if (typeof value !== 'string')\n {\n throw new ContractTypeError();\n }\n\n return value;\n}\n\nfunction integer(value: CanonicalValue | undefined): bigint\n{\n if (typeof value !== 'bigint')\n {\n throw new ContractTypeError();\n }\n\n return value;\n}\n\n// ============================================================================\n// Encoding\n// ============================================================================\n\nexport function encodeHandshakeResponse(sessionId: string, expiresAtMillis: bigint): CanonicalValue\n{\n return new Map<string, CanonicalValue>([\n ['sessionId', sessionId],\n ['expiresAtMillis', expiresAtMillis],\n ]);\n}\n\nexport function encodeEchoResponse(message: string, sequence: bigint, serverTimeMillis: bigint): CanonicalValue\n{\n return new Map<string, CanonicalValue>([\n ['message', message],\n ['sequence', sequence],\n ['serverTimeMillis', serverTimeMillis],\n ]);\n}\n\nexport function encodeListItemsResponse(items: ContractItem[], nextCursor: string | null): CanonicalValue\n{\n const encodedItems: CanonicalValue = items.map((item) => new Map<string, CanonicalValue>([\n ['id', item.id],\n ['name', item.name],\n ['updatedAtMillis', item.updatedAtMillis],\n ]));\n const members = new Map<string, CanonicalValue>([['items', encodedItems]]);\n if (nextCursor !== null)\n {\n members.set('nextCursor', nextCursor);\n }\n\n return members;\n}\n","/**\n * The dev server's test hooks, mirroring the spfn-mobile reference server's\n * `/control` surface route for route so the mobile integration suites can\n * drive either server with only a URL change.\n *\n * `/control` is NOT part of the contract: nothing under it appears in the\n * bundle, no SDK knows it exists, and its answers are plain objects rather\n * than contract envelopes. Every route except the readiness probe requires\n * the per-launch token; the token is never logged.\n *\n * @module server/client-proof/dev-control\n */\nimport { encodeCanonicalJson, parseCanonicalJson, type CanonicalValue } from './canonical-json';\nimport { ClientProofState, TestClock } from './state';\n\nexport const CONTROL_PREFIX = '/control/';\n\nexport const CONTROL_TOKEN_HEADER = 'x-spfn-reference-control';\n\nconst HTTP_OK = 200;\nconst HTTP_BAD_REQUEST = 400;\nconst HTTP_FORBIDDEN = 403;\nconst HTTP_NOT_FOUND = 404;\nconst HTTP_CONFLICT = 409;\n\nconst MAX_CONTROL_BODY_BYTES = 4096;\n\nexport async function handleControlRequest(\n state: ClientProofState,\n controlToken: string,\n path: string,\n request: Request,\n): Promise<Response>\n{\n if (path === '/control/health')\n {\n return answer(HTTP_OK, new Map<string, CanonicalValue>([['status', 'ok']]));\n }\n if (request.headers.get(CONTROL_TOKEN_HEADER) !== controlToken)\n {\n return answer(HTTP_FORBIDDEN, failure('control token'));\n }\n\n const raw = new Uint8Array(await request.arrayBuffer());\n const body = raw.length > MAX_CONTROL_BODY_BYTES ? raw.slice(0, MAX_CONTROL_BODY_BYTES) : raw;\n\n switch (path)\n {\n case '/control/stats':\n return stats(state);\n case '/control/reset':\n state.reset();\n\n return ok();\n case '/control/expire-sessions':\n state.expireSessions();\n\n return ok();\n case '/control/register-key':\n return registerKey(state, body);\n case '/control/revoke-key':\n return revokeKey(state, body);\n case '/control/session-ttl':\n return sessionTtl(state, body);\n case '/control/hold':\n return hold(state, body);\n case '/control/advance-clock':\n return advanceClock(state, body);\n default:\n return answer(HTTP_NOT_FOUND, failure('unknown control route'));\n }\n}\n\n// ---- routes ----------------------------------------------------------------\n\nfunction stats(state: ClientProofState): Response\n{\n const counters = state.stats();\n\n return answer(HTTP_OK, withOk(new Map<string, CanonicalValue>([\n ['echoCount', BigInt(counters.echoCount)],\n ['handshakeCount', BigInt(counters.handshakeCount)],\n ['itemsListCount', BigInt(counters.itemsListCount)],\n ['liveSessionCount', BigInt(counters.liveSessionCount)],\n ['refusalCount', BigInt(counters.refusalCount)],\n ['requestCount', BigInt(counters.requestCount)],\n ['spentNonceCount', BigInt(counters.spentNonceCount)],\n ])));\n}\n\n/**\n * Registers the public key a test client generated — the asymmetric\n * counterpart of the shared-key provisioning the HMAC profile injected at\n * construction. The body carries only the public half (SPKI DER base64); no\n * secret ever crosses this route.\n */\nfunction registerKey(state: ClientProofState, body: Uint8Array): Response\n{\n const keyId = stringField(body, 'keyId');\n const publicKey = stringField(body, 'publicKey');\n if (keyId === null)\n {\n return badRequest('keyId');\n }\n if (publicKey === null)\n {\n return badRequest('publicKey');\n }\n try\n {\n state.registerPublicKey(keyId, publicKey);\n }\n catch\n {\n return badRequest('publicKey');\n }\n\n return ok();\n}\n\nfunction revokeKey(state: ClientProofState, body: Uint8Array): Response\n{\n const keyId = stringField(body, 'keyId');\n if (keyId === null)\n {\n return badRequest('keyId');\n }\n state.revokeKey(keyId);\n\n return ok();\n}\n\nfunction sessionTtl(state: ClientProofState, body: Uint8Array): Response\n{\n const ttlMillis = integerField(body, 'ttlMillis');\n if (ttlMillis === null)\n {\n return badRequest('ttlMillis');\n }\n state.setSessionTtlMillis(Number(ttlMillis));\n\n return ok();\n}\n\nfunction hold(state: ClientProofState, body: Uint8Array): Response\n{\n const path = stringField(body, 'path');\n const millis = integerField(body, 'millis');\n const count = integerField(body, 'count');\n if (path === null)\n {\n return badRequest('path');\n }\n if (millis === null)\n {\n return badRequest('millis');\n }\n if (count === null)\n {\n return badRequest('count');\n }\n state.holdPath(path, Number(millis), Number(count));\n\n return ok();\n}\n\n/**\n * Moves a test clock forward. Refused when the server runs on the wall clock,\n * because silently doing nothing is how a test passes for the wrong reason.\n */\nfunction advanceClock(state: ClientProofState, body: Uint8Array): Response\n{\n const clock = state.clockRef;\n if (!(clock instanceof TestClock))\n {\n return answer(HTTP_CONFLICT, failure('server is running on the system clock'));\n }\n const millis = integerField(body, 'millis');\n if (millis === null)\n {\n return badRequest('millis');\n }\n clock.advance(Number(millis));\n\n return ok();\n}\n\n// ---- plumbing --------------------------------------------------------------\n\nfunction members(body: Uint8Array): Map<string, CanonicalValue> | null\n{\n if (body.length === 0)\n {\n return new Map();\n }\n let parsed: CanonicalValue;\n try\n {\n parsed = parseCanonicalJson(body);\n }\n catch\n {\n return null;\n }\n\n return parsed instanceof Map ? parsed : null;\n}\n\nfunction stringField(body: Uint8Array, field: string): string | null\n{\n const value = members(body)?.get(field);\n\n return typeof value === 'string' ? value : null;\n}\n\nfunction integerField(body: Uint8Array, field: string): bigint | null\n{\n const value = members(body)?.get(field);\n\n return typeof value === 'bigint' ? value : null;\n}\n\nfunction badRequest(field: string): Response\n{\n return answer(HTTP_BAD_REQUEST, failure(`missing or malformed field: ${field}`));\n}\n\nfunction ok(): Response\n{\n return answer(HTTP_OK, withOk(new Map()));\n}\n\nfunction failure(reason: string): Map<string, CanonicalValue>\n{\n return new Map<string, CanonicalValue>([['ok', false], ['reason', reason]]);\n}\n\nfunction withOk(extra: Map<string, CanonicalValue>): Map<string, CanonicalValue>\n{\n extra.set('ok', true);\n\n return extra;\n}\n\nfunction answer(status: number, value: Map<string, CanonicalValue>): Response\n{\n const bytes = encodeCanonicalJson(value);\n const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n\n return new Response(buffer, { status, headers: { 'content-type': 'application/json' } });\n}\n","/**\n * The mobile contract bundle — what spfn-mobile's Swift/Kotlin codegen reads.\n *\n * SPFN primitives owns the contract; this module assembles the bundle so the\n * export is produced here rather than transcribed in the consumer. Two kinds of\n * value go into it:\n *\n * - **Derived.** Operations, wire headers, proof-input fields, replay window,\n * HTTP statuses and core.time's response shape are read from the modules that\n * implement them. Changing the server changes the export.\n * - **Declared.** The remaining type shapes, error summaries and prose that\n * describes canonicalization and admission are written here. No runtime value\n * carries them. `contract-export.test.ts` runs the real decoders and encoders\n * against every declaration, so one that stops describing the server fails.\n *\n * @module server/client-proof/contract-bundle\n */\nimport { createHash } from 'node:crypto';\n\nimport {\n CORE_TIME_OPERATION_ID,\n ServerTimeResponseSchema,\n} from '@spfn/core/server';\n\nimport { KEY_TTL_DAYS } from '../lib/key-policy';\nimport { KEY_ALGORITHM } from '../types';\nimport { CLIENT_PROOF_CONTENT_TYPE, CLIENT_PROOF_HEADERS } from './admission';\nimport {\n AUTH_SURFACE_OPERATIONS,\n CONTRACT_OPERATIONS,\n CORE_PREREQUISITE_OPERATIONS,\n IMPORTED_CORE_TIME_CONTRACT,\n} from './contract-types';\nimport {\n CLIENT_PROOF_PROFILE,\n DEFAULT_REPLAY_WINDOW_MILLIS,\n PROOF_INPUT_FIELDS,\n PROOF_INPUT_SEPARATOR,\n} from './proof';\nimport { CLIENT_PROOF_ERROR_CODES, HTTP_STATUS } from './refusal';\nimport { CLIENT_IDENTITY_HEADERS, CLIENT_KINDS, SERVER_CONTRACT_HEADERS } from './wire-headers';\n\n/**\n * The version this export publishes. A mistake becomes a new version.\n *\n * The line is 0.x on purpose. The contract has one consumer, it is still\n * alpha, and its first export shipped a type spelling the consumer could not\n * parse — a surface that green has not earned a stable major. Under 0.x a\n * breaking change is a minor bump, which is what that correction actually was;\n * publishing it as 1.0.1 called a breaking change a patch.\n *\n * 1.0.0 and 1.0.1 existed briefly and are withdrawn. Neither was consumed.\n *\n * 0.2.0 revises the proof mechanism from HMAC-SHA-256 (a shared key) to ECDSA\n * P-256 (a registered public key) — breaking, hence a minor bump, taken while\n * the consumer count is zero. The proof-input, wire headers, admission order\n * and error codes are unchanged.\n *\n * 0.3.0 exports the existing `/_auth` enrollment surface (register, login,\n * native OAuth, key rotation) as contract operations, introduces the unproven\n * operation class (`authProfile: 'none'`), the `boolean` scalar the enrollment\n * responses need, and the key-TTL metadata. A surface addition under 0.x is a\n * minor bump. The clientProofV1 profile itself is unchanged from 0.2.0.\n *\n * 0.3.1 adds the optional `accessToken` field to `OauthNativeRequest`, which\n * Kakao needs to resolve an email claim its id_token omits. A patch, not a\n * minor: nothing existing changes meaning, a generated consumer that never\n * sends the field still matches the server, and the supported range is\n * unchanged — so a consumer pinned at 0.3.0 stays inside it rather than\n * falling out of a range it is in fact still compatible with.\n *\n * 0.4.0 binds `OauthNativeRequest.nonce` to the key being enrolled: it must be\n * the `fingerprint` of the submitted `publicKey`. The field list is untouched,\n * but a consumer that mints a random nonce is now refused, so this is breaking\n * and the range moves with it. Without the binding a valid id_token is enough\n * to enroll any key — the token is bearer-shaped and travels, while the web\n * OAuth flow keeps its key inside CSRF-bound encrypted state.\n *\n * 0.4.1 adds the key-management operations (list, revoke, revoke-all) and the\n * types they carry. A patch: existing operations and types are untouched, and a\n * consumer generated against 0.4.0 keeps matching the server, so the supported\n * range does not move. All three are POST with their arguments in the body —\n * the proof signs body bytes, which have a canonicalization rule, while a value\n * in the path does not.\n *\n * 0.4.2 gives the REST surface a readable failure: every error response now\n * carries the `{\"error\":{\"code\",\"message\",\"requestId\"}}` envelope next to the\n * web fields, and `auth.enroll.oauthNative`'s twelve refusals are listed as\n * codes with their status and retryability. A patch: no request or response\n * type moves, and a consumer generated against 0.4.1 could not read these\n * failures at all — it saw one undecodable body whatever went wrong — so\n * nothing it relies on changes and the supported range stays put.\n *\n * 0.5.0 widens the type grammar so an app contract can describe shapes the auth\n * surface never needed — a floating-point `number`, `map<string,T>`, and a named\n * enum whose declaration carries values instead of fields — and states the date\n * convention rather than adding a date scalar: a moment is an integer of\n * milliseconds since the Unix epoch in a field whose name ends `AtMillis`.\n *\n * Widening the grammar alone would have been a patch. What makes this breaking is\n * that `KeySummary` did not follow the convention: `createdAt`, `lastUsedAt`,\n * `expiresAt` and `revokedAt` were ISO 8601 strings, so the same contract stated\n * one representation and shipped two. They are now `createdAtMillis`,\n * `lastUsedAtMillis`, `expiresAtMillis` and `revokedAtMillis` integers, and\n * `listKeys` returns milliseconds.\n *\n * Taken now because the cost only grows: no generated consumer reads these types\n * yet — spfn-mobile's codegen path is unbuilt — and an exception documented\n * instead of removed would have kept Swift's `ISO8601DateFormatter` rejecting\n * fractional seconds as a live way for the two SDKs to disagree, on exactly\n * these four fields. An app reading `createdAt` from `authApi.listKeys()` must\n * move to `createdAtMillis`.\n *\n * 0.6.0 puts the contract version on the wire. A client states its kind, its own\n * release and the contract version it was generated from; the server answers on\n * every response with the version it serves and the range it accepts. A client\n * that ships separately from the server and states no contract version is\n * refused — until now the disagreement surfaced as an undecodable body, which\n * told the user nothing. None of it enters the proof input.\n *\n * `algorithm` becomes the `KeyAlgorithm` enum in the three requests that carry it\n * and in `KeySummary`. The routes have always constrained it to those values while\n * this contract said `string`, so the contract understated the server; it is\n * breaking because it changes what codegen produces for an existing field.\n *\n * The grammar also stops telling a consumer what to do with a value outside a\n * declared set. That was an instruction to the decoder, and a contract states\n * what the server does — how to survive a list that grows is the client's\n * decision to make. No list here is promised to be closed: an algorithm can be\n * withdrawn for a weakness found after this was written, and a contract that\n * promised otherwise would be promising something it cannot keep.\n *\n * 0.6.1 records when each operation became available: every operation now carries\n * `since`, the contract version it first appeared in, backfilled from this\n * repository's own history, and the optional `deprecatedIn` / `removedIn` that a\n * later version will fill in. Nothing is deprecated today, so both are absent\n * everywhere.\n *\n * A patch: no request or response type moves, no operation is added or taken\n * away, and this contract's policy is `allOrNothing`, so the new fields change no\n * verdict — a client is still admitted or refused by one version for the whole\n * surface. They are here so a deprecation has somewhere to be recorded when the\n * first one happens, and so an app contract, which decides per operation, reads\n * availability in the same shape rather than inventing a second one.\n *\n * 0.7.0 removes the `number` scalar and gives the grammar `decimal<scale>`: the\n * wire value is an integer and what it means is that integer divided by 10 to the\n * scale, so `decimal<2>` carries 1999 for 19.99. Canonical JSON does not move —\n * it already admits signed 64-bit integers only and calls a fraction an error,\n * which is what a `number` field would have had to be written as. The grammar and\n * the encoding had been stating different things, and only the encoding ran.\n *\n * Two rules ride the spelling. The scale is part of the type, so changing it is\n * breaking and takes a version bump, and the field is renamed to carry its new\n * unit rather than be quietly remeasured under the old name — the same reasoning\n * that put `AtMillis` in the name of every moment here. And a generator emits a\n * decimal type — Swift `Decimal`, Kotlin `BigDecimal` — never a binary float, and\n * rejects a value finer than the declared scale at encoding time instead of\n * rounding it, because rounding lets the client decide what a value the server\n * declared exactly is worth.\n *\n * Breaking because a declared scalar is gone. A consumer generated against 0.6.x\n * that meets `decimal<2>` fails at generation time, which is what this grammar's\n * own rule asks for — an unknown spelling is a contract error, not something to\n * guess at. Nothing deployed breaks: no type in this contract used `number`, so\n * the removal has zero usages, and it is taken now because the alternative is\n * carrying a scalar the encoding refuses until something depends on it.\n *\n * 0.8.0 applies to the error envelope the rule 0.6.0 applied to the grammar.\n * `unknownCodePolicy: 'reject'` and the rule beside it told a decoder what to do\n * with a code this bundle does not list, and `additionalFields` told it to ignore\n * the extra top-level fields rather than reject them. The test is whether the\n * server would notice a client doing the opposite, and it would not. Both are\n * replaced by the fact behind them: the server sends codes outside the list, and\n * the body carries fields beside the error object. Breaking, because removing a\n * declaration changes what a generated consumer is built from.\n *\n * 0.9.0 imports core.time as the bodyless, unproven prerequisite a client uses\n * to establish the server epoch before minting its first proof in a process.\n * It also states fail-closed behavior when synchronization is unavailable and\n * pins all four time-admission boundaries. Breaking because operation request\n * types were previously mandatory and every generated call therefore carried a\n * body; the supported range moves so an older generator cannot guess at GET.\n */\nexport const CONTRACT_VERSION = '0.9.0';\nexport const CONTRACT_MAJOR = 0;\nexport const CONTRACT_NAME = 'spfn-mobile-contract';\n\n/**\n * Under 0.x the minor carries breaking changes, so the range stops at 0.9.0.\n *\n * 0.9.0 moves the floor because it adds a bodyless GET operation. A consumer\n * generated against 0.8.x requires requestType on every operation and would\n * have to guess how to send core.time, so it is refused CONTRACT_UNSUPPORTED.\n */\nexport const CONTRACT_SUPPORTED_RANGE = '>=0.9.0 <0.10.0';\n\n/** What spfn-mobile's validator expects an upstream-exported bundle to name. */\nexport const EXPORT_ORIGIN = 'spfn-primitives-ci-export';\n\n/**\n * Bumped whenever the assembled shape changes, independent of the contract.\n *\n * The bump follows what a reader of this shape can still find. A major when a\n * section or key is removed or renamed, so code reading it stops finding what it\n * read; a minor when the shape only grows. 5.0.0 is a major because `typeGrammar`\n * lost `integerVersusNumber`, where 4.1.0 was a minor for availability fields that\n * were purely added.\n */\nexport const EXPORTER_VERSION = '@spfn/auth/contract-bundle@5.1.0';\n\n/**\n * The scalars the grammar admits.\n *\n * There is no floating-point scalar. A fractional value is `decimal<scale>`, an\n * integer on the wire with its scale declared in the type, because canonical JSON\n * carries signed 64-bit integers only and treats a fraction as an error — a\n * floating-point scalar was a shape the encoding would have refused. `integer`\n * stays separate from it so a count is never given a scale it does not have.\n *\n * There is no date scalar. A moment is an integer of milliseconds since the Unix\n * epoch in a field whose name ends `AtMillis`, which is what every existing type\n * already does.\n */\ntype ScalarTypeName = 'string' | 'integer' | 'boolean';\n\n/**\n * The scales `decimal<scale>` admits.\n *\n * Scale 0 is `integer` written the long way, so it is not a scale. The ceiling is\n * 18 because 10^18 is the largest power of ten a signed 64-bit integer holds, and\n * the wire value is such an integer — above 18 there is no integer part left to\n * carry.\n *\n * Spelled as a union rather than checked at runtime so an out-of-range scale\n * fails to compile here, where the declaration is written, rather than reaching a\n * consumer's generator as a type it cannot parse.\n */\ntype DecimalScale =\n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9\n | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18;\n\n/**\n * A fixed-point value: the integer on the wire divided by 10 to the scale.\n *\n * Parameterized like `array<T>`, and read by the same parser — a consumer that\n * does not recognise the prefix reads `decimal<2>` as a type named \"decimal<2>\"\n * and fails at compile time, which is the grammar's rule for an unknown spelling.\n */\ntype DecimalTypeName = `decimal<${DecimalScale}>`;\n\n/**\n * Declared names a field is allowed to reference — the types in\n * `CONTRACT_TYPES` and the enums in `CONTRACT_ENUMS` that are actually used.\n *\n * Hand-listed rather than derived: the declarations below are what would define\n * it, so deriving it would be circular, and a misspelled name has to fail here\n * rather than reach the consumer as a type it cannot find.\n */\ntype ReferencedTypeName = 'Item' | 'KeySummary' | 'KeyAlgorithm';\n\ntype ElementTypeName = ScalarTypeName | DecimalTypeName | ReferencedTypeName;\n\n/**\n * The field-type grammar the consumer's codegen parses.\n *\n * `array<T>` and `map<string,T>` are the only container spellings: spfn-mobile's\n * `FieldType.parse` reads a recognised container prefix as a container and\n * everything else as a named type, so `Item[]` would silently become a type\n * named \"Item[]\" and fail at compile time rather than at parse time.\n *\n * A map's key is always a string because JSON has no other key type. Spelling it\n * out anyway keeps the consumer from having to assume it.\n *\n * This union is narrower than the grammar it guards: the grammar lets a\n * container hold another container, and the consumer's parser recurses, while\n * here a container holds one element type. Narrower is the safe direction —\n * nothing invalid can be declared — and it widens when a nested container is\n * first needed.\n */\ntype FieldTypeName =\n | ElementTypeName\n | `array<${ElementTypeName}>`\n | `map<string,${ElementTypeName}>`;\n\ninterface FieldDeclaration\n{\n name: string;\n type: FieldTypeName;\n optional: boolean;\n}\n\ninterface TypeDeclaration\n{\n name: string;\n fields: FieldDeclaration[];\n}\n\n/**\n * A named set of string values, declared by name so a field can reference it the\n * same way it references an object type.\n *\n * The values are the ones the server accepts and sends **now**. A set is not\n * promised to stay as it is: an algorithm can be added, and one can be withdrawn\n * for a weakness found after this was written. What a consumer does when it meets\n * a value it does not know is the consumer's decision — a generated client that\n * cannot survive a grown list is a defect in the generator, not something this\n * contract can prevent by declaring the set closed.\n */\ninterface EnumDeclaration\n{\n name: string;\n values: readonly string[];\n}\n\nfunction required(name: string, type: FieldDeclaration['type']): FieldDeclaration\n{\n return { name, type, optional: false };\n}\n\nfunction optional(name: string, type: FieldDeclaration['type']): FieldDeclaration\n{\n return { name, type, optional: true };\n}\n\n/**\n * Translate the imported core response schema into this bundle's deliberately\n * small type grammar. A core change outside that grammar fails export here\n * rather than leaving auth to publish a plausible but different response.\n */\nfunction coreTimeResponseDeclaration(): TypeDeclaration\n{\n if (ServerTimeResponseSchema.type !== 'object'\n || ServerTimeResponseSchema.additionalProperties !== false)\n {\n throw new Error('core.time response must remain a closed object');\n }\n\n const requiredFields = new Set(ServerTimeResponseSchema.required);\n const fields = Object.entries(ServerTimeResponseSchema.properties).map(([name, schema]) =>\n {\n if (schema.type !== 'integer')\n {\n throw new Error(`core.time response field ${name} is outside the mobile type grammar`);\n }\n\n return {\n name,\n type: 'integer' as const,\n optional: !requiredFields.has(name),\n };\n });\n\n return { name: 'ServerTimeResponse', fields };\n}\n\n/**\n * The contract types.\n *\n * `ServerTimeResponse` is translated from core's exported TypeBox schema above.\n * The clientProofV1 request types mirror the decoders in `contract-types.ts`\n * and the response types mirror the encoders. Neither reads this table — the\n * conformance vectors are what hold the two in agreement.\n *\n * The `/_auth` surface types mirror the TypeBox route schemas (input body +\n * Next.js interceptor body merged, since a mobile client sends the whole\n * body itself) and the service result interfaces. The optional free-form\n * extension fields (`metadata`, `profile`) are deliberately not declared:\n * they are outside this grammar, the server tolerates their absence, and a\n * consumer generated from this contract never needs to send them.\n */\nexport const CONTRACT_TYPES: readonly TypeDeclaration[] = [\n coreTimeResponseDeclaration(),\n {\n name: 'HandshakeRequest',\n fields: [\n required('clientId', 'string'),\n required('keyId', 'string'),\n required('nonce', 'string'),\n required('issuedAtMillis', 'integer'),\n ],\n },\n {\n name: 'HandshakeResponse',\n fields: [\n required('sessionId', 'string'),\n required('expiresAtMillis', 'integer'),\n ],\n },\n {\n name: 'EchoRequest',\n fields: [\n required('message', 'string'),\n required('sequence', 'integer'),\n ],\n },\n {\n name: 'EchoResponse',\n fields: [\n required('message', 'string'),\n required('sequence', 'integer'),\n required('serverTimeMillis', 'integer'),\n ],\n },\n {\n name: 'ListItemsRequest',\n fields: [\n required('limit', 'integer'),\n optional('cursor', 'string'),\n ],\n },\n {\n name: 'Item',\n fields: [\n required('id', 'string'),\n required('name', 'string'),\n required('updatedAtMillis', 'integer'),\n ],\n },\n {\n name: 'ListItemsResponse',\n fields: [\n required('items', 'array<Item>'),\n optional('nextCursor', 'string'),\n ],\n },\n {\n name: 'RegisterRequest',\n fields: [\n optional('email', 'string'),\n optional('phone', 'string'),\n required('verificationToken', 'string'),\n required('password', 'string'),\n required('publicKey', 'string'),\n required('keyId', 'string'),\n required('fingerprint', 'string'),\n required('algorithm', 'KeyAlgorithm'),\n ],\n },\n {\n name: 'RegisterResponse',\n fields: [\n required('userId', 'string'),\n required('publicId', 'string'),\n optional('email', 'string'),\n optional('phone', 'string'),\n ],\n },\n {\n name: 'LoginRequest',\n fields: [\n optional('email', 'string'),\n optional('phone', 'string'),\n required('password', 'string'),\n required('publicKey', 'string'),\n required('keyId', 'string'),\n required('fingerprint', 'string'),\n required('algorithm', 'KeyAlgorithm'),\n optional('oldKeyId', 'string'),\n ],\n },\n {\n name: 'LoginResponse',\n fields: [\n required('userId', 'string'),\n required('publicId', 'string'),\n optional('email', 'string'),\n optional('phone', 'string'),\n required('passwordChangeRequired', 'boolean'),\n ],\n },\n {\n name: 'OauthNativeRequest',\n fields: [\n required('idToken', 'string'),\n required('nonce', 'string'),\n optional('accessToken', 'string'),\n required('publicKey', 'string'),\n required('keyId', 'string'),\n required('fingerprint', 'string'),\n required('algorithm', 'KeyAlgorithm'),\n ],\n },\n {\n name: 'OauthNativeResponse',\n fields: [\n required('userId', 'string'),\n required('keyId', 'string'),\n required('isNewUser', 'boolean'),\n ],\n },\n {\n name: 'RotateKeyRequest',\n fields: [\n required('publicKey', 'string'),\n required('keyId', 'string'),\n required('fingerprint', 'string'),\n required('algorithm', 'KeyAlgorithm'),\n ],\n },\n {\n name: 'RotateKeyResponse',\n fields: [\n required('success', 'boolean'),\n required('keyId', 'string'),\n ],\n },\n {\n name: 'ListKeysRequest',\n fields: [\n optional('includeRevoked', 'boolean'),\n ],\n },\n {\n name: 'KeySummary',\n fields: [\n required('keyId', 'string'),\n optional('deviceName', 'string'),\n optional('platform', 'string'),\n required('algorithm', 'KeyAlgorithm'),\n required('fingerprintPrefix', 'string'),\n required('createdAtMillis', 'integer'),\n optional('lastUsedAtMillis', 'integer'),\n optional('expiresAtMillis', 'integer'),\n required('isExpired', 'boolean'),\n required('isActive', 'boolean'),\n optional('revokedAtMillis', 'integer'),\n ],\n },\n {\n name: 'ListKeysResponse',\n fields: [\n required('keys', 'array<KeySummary>'),\n ],\n },\n {\n name: 'RevokeKeyRequest',\n fields: [\n required('keyId', 'string'),\n ],\n },\n {\n name: 'RevokeKeyResponse',\n fields: [\n required('keyId', 'string'),\n required('selfRevoked', 'boolean'),\n ],\n },\n {\n name: 'RevokeAllKeysRequest',\n fields: [\n optional('includeCurrent', 'boolean'),\n ],\n },\n {\n name: 'RevokeAllKeysResponse',\n fields: [\n required('revokedCount', 'integer'),\n required('currentKeyRevoked', 'boolean'),\n ],\n },\n];\n\n/**\n * The enums this contract declares.\n *\n * `KeyAlgorithm` is read from the server's own list rather than transcribed, so\n * an algorithm added or withdrawn there moves this declaration with it.\n */\nexport const CONTRACT_ENUMS: readonly EnumDeclaration[] = [\n { name: 'KeyAlgorithm', values: [...KEY_ALGORITHM] },\n];\n\n/** One line per code describing what it means on the wire. */\nconst ERROR_SUMMARIES: Record<string, string> = {\n PROOF_INVALID: 'the client proof did not verify',\n PROOF_REPLAYED: 'the nonce was already used inside the replay window',\n PROOF_EXPIRED: 'issuedAtMillis falls outside the replay window',\n SESSION_REVOKED: 'the key or session was revoked',\n PROFILE_REJECTED: 'an auth profile outside the allowlist was named',\n CONTRACT_UNSUPPORTED: 'the request is not the shape this contract describes',\n};\n\n/**\n * No refusal is retryable without changing the request.\n *\n * An auth-family code clears after a fresh handshake, which is a different\n * request, so replaying the same bytes never helps.\n */\nconst RETRYABLE = false;\n\ninterface RestSurfaceError\n{\n code: string;\n httpStatus: number;\n retryable: boolean;\n summary: string;\n}\n\n/**\n * Every way `auth.enroll.oauthNative` refuses, as codes a consumer can switch on.\n *\n * \"Every way\" includes the app's own `beforeRegister` check: what that check\n * decides is the app's business, but the response it produces is the\n * framework's — a fixed class name at a fixed status. Leaving it out would hand\n * every app that uses the hook an undecodable refusal.\n *\n * The codes are the server's own error class names rather than a second\n * vocabulary invented for mobile: two vocabularies would have to be kept in\n * step, and the mapping between them is exactly the place a wrong answer\n * hides.\n *\n * Only this operation's codes are listed. The `error` envelope now reaches\n * every REST operation, but a code list is a promise, and a promise about\n * routes whose failure paths have not been enumerated one by one would be a\n * guess. That the server sends codes outside this list is stated as\n * `unlistedCodes`; what a decoder does when it meets one is the decoder's\n * decision.\n */\nconst REST_SURFACE_ERRORS: readonly RestSurfaceError[] = [\n {\n code: 'ValidationError',\n httpStatus: 400,\n retryable: false,\n summary: 'the request body is not the shape the operation declares',\n },\n {\n code: 'NativeSignInUnsupportedError',\n httpStatus: 400,\n retryable: false,\n summary: 'this provider has no native id_token sign-in — a server configuration fact, not a user error',\n },\n {\n code: 'NonceKeyBindingError',\n httpStatus: 400,\n retryable: false,\n summary: 'the nonce is not the fingerprint of the submitted public key',\n },\n {\n code: 'InvalidKeyFingerprintError',\n httpStatus: 400,\n retryable: false,\n summary: 'the fingerprint is not the hash of the submitted public key',\n },\n {\n code: 'UnverifiedEmailLinkError',\n httpStatus: 400,\n retryable: false,\n summary: 'that email already has an account and the provider never verified it, so linking is refused',\n },\n {\n code: 'InvalidSocialTokenError',\n httpStatus: 401,\n retryable: false,\n summary: 'the id_token failed signature, issuer, audience, expiry, nonce or subject verification',\n },\n {\n code: 'AccountDisabledError',\n httpStatus: 403,\n retryable: false,\n summary: 'the account cannot open a session in its current status',\n },\n {\n code: 'AccountPendingDeletionError',\n httpStatus: 403,\n retryable: false,\n summary: 'the account is scheduled for deletion and must be restored before it can sign in',\n },\n {\n code: 'RegistrationRejectedError',\n httpStatus: 403,\n retryable: false,\n summary:\n 'the app refused this sign-up in its own beforeRegister check — reached only when the identity would '\n + 'create a new account, never when it links to an existing one',\n },\n {\n code: 'KeyIdAlreadyRegisteredError',\n httpStatus: 409,\n retryable: false,\n summary: 'that keyId is taken or was revoked — generate a fresh keyId and retry',\n },\n {\n code: 'TooManyRequestsError',\n httpStatus: 429,\n retryable: true,\n summary: 'the rate limit for this endpoint was exceeded; the same request succeeds after the window',\n },\n {\n code: 'Error',\n httpStatus: 500,\n retryable: false,\n summary: 'the server failed for a reason it does not describe to the client',\n },\n];\n\nexport interface MobileContractBundle\n{\n [key: string]: unknown;\n}\n\n/** Assembles the bundle. Pure — same inputs, same object, every time. */\nexport function buildMobileContractBundle(): MobileContractBundle\n{\n return {\n bundleKind: 'UPSTREAM_EXPORT',\n origin: EXPORT_ORIGIN,\n originStatement:\n 'Generated from the route and contract definitions in SPFN primitives '\n + '(packages/auth/src/server/client-proof) and published from that repository. '\n + 'This file is generated output: edit the source modules and re-export, never this file.',\n contractName: CONTRACT_NAME,\n contractMajor: CONTRACT_MAJOR,\n contractVersion: CONTRACT_VERSION,\n supportedRange: CONTRACT_SUPPORTED_RANGE,\n exporterVersion: EXPORTER_VERSION,\n authProfiles: {\n allowed: [CLIENT_PROOF_PROFILE],\n unknownProfilePolicy: 'reject',\n mixingWithinSession: 'prohibited',\n },\n operationAuthClasses: {\n none:\n 'the unproven class: the operation is accepted with neither proof headers nor a session header, '\n + 'because it is called before any proof can be minted (clock synchronization, enrollment and login)',\n [CLIENT_PROOF_PROFILE]:\n 'the operation is admitted by the clientProofV1 admission order; requiresSession states whether '\n + 'the session header travels',\n rule:\n 'an operation whose authProfile is not none refuses an unproven call exactly as it refuses any '\n + 'failed admission; nothing is downgraded to anonymous handling',\n },\n operationAvailability: {\n since:\n 'the contract version the operation first appeared in. Every operation carries one, and it is '\n + 'never rewritten: it is a fact about this contract\\'s history',\n deprecatedIn:\n 'the contract version that marked the operation deprecated, absent until one does. A deprecated '\n + 'operation is still served — the mark opens the grace period, it does not end the operation',\n removedIn:\n 'the contract version that removed the operation. A removed operation leaves this list, so no '\n + 'entry carries it today; it is the field a removal is recorded in when the first one happens',\n ordering:\n 'since <= deprecatedIn < removedIn, and removedIn never appears without deprecatedIn: an '\n + 'operation is marked in one version and taken away in a later one, never both at once',\n verdictRule:\n 'under this contract\\'s allOrNothing policy these three fields decide nothing. One contract '\n + 'version passes or refuses this whole surface, so availability here is description a reader '\n + 'and a changelog use, not an input the server compares against. A contract whose policy is '\n + 'perOperation reads the same fields as a verdict input',\n procedure:\n 'a removal is mark then wait then remove: deprecatedIn in one version, the operation still '\n + 'served, removedIn in a later one. Nothing is removed in the version that first deprecates it',\n },\n keyPolicy: {\n ttlDays: KEY_TTL_DAYS,\n rotationOperation: 'auth.keys.rotate',\n rule:\n 'a registered public key expires ttlDays after registration; an expired or revoked key is refused '\n + 'at the revocation step (SESSION_REVOKED, non-disclosing), so the client rotates its key via the '\n + 'rotation operation before the TTL runs out',\n },\n clockSynchronization: {\n appliesTo: CLIENT_PROOF_PROFILE,\n operation: CORE_TIME_OPERATION_ID,\n source: {\n package: '@spfn/core',\n routeContractSince: IMPORTED_CORE_TIME_CONTRACT.sourceSince,\n },\n phase: 'before minting the first proof in each client process',\n epochField: 'serverTimeMillis',\n requestBody: 'none',\n responseBody: 'the ServerTimeResponse type as plain JSON',\n unavailableBehavior: 'failClosed',\n fallbackClock: 'prohibited',\n failureRule:\n 'when core.time is unavailable or its response cannot be decoded, the client does not mint or send '\n + 'a proof; it never silently falls back to an unsynchronized device wall clock',\n persistenceRule:\n 'the synchronization requirement is process-local; this contract specifies no persistent offset, '\n + 'retry sleep, or device-specific margin',\n admissionBoundaries: [\n { serverNowMinusIssuedAtMillis: 0, outcome: 'accept' },\n { serverNowMinusIssuedAtMillis: -1, outcome: 'PROOF_EXPIRED' },\n { serverNowMinusIssuedAtMillis: DEFAULT_REPLAY_WINDOW_MILLIS, outcome: 'accept' },\n { serverNowMinusIssuedAtMillis: DEFAULT_REPLAY_WINDOW_MILLIS + 1, outcome: 'PROOF_EXPIRED' },\n ],\n },\n nativeEnrollment: {\n appliesTo: 'auth.oauth.native',\n nonceRule:\n 'the nonce sent with a native id_token must be the fingerprint field of the same request, which '\n + 'is the SHA-256 of the DER bytes of publicKey in lowercase hex; the server refuses the call '\n + 'when the two differ or when the fingerprint is not that key\\'s hash',\n appleVariant:\n 'Apple hashes the nonce it receives, so the client puts sha256hex(fingerprint) in Apple\\'s '\n + 'authorization request while still sending the raw fingerprint as nonce; every other provider '\n + 'carries the raw value both ways',\n rationale:\n 'an id_token is bearer-shaped and travels, so verifying it alone lets whoever holds one enroll '\n + 'any key on that account; deriving the nonce from the key means a stolen id_token carries the '\n + 'victim\\'s fingerprint and cannot be paired with the attacker\\'s key',\n },\n restOperations: {\n appliesTo: 'every operation whose path starts with /_auth',\n requestBody:\n 'plain JSON of the request type, validated server-side; canonical-JSON encoding is required only '\n + 'when the call is proven (the proof binds the canonical bytes)',\n responseBody: 'the response type as plain JSON, with no envelope around it',\n errorEnvelope:\n 'the same {\"error\":{\"code\",\"message\",\"requestId\"}} envelope every operation uses, carried '\n + 'alongside the SPFN web fields (__type and the error class\\'s own public fields) in one body: '\n + 'the web client restores an error class from __type while a generated client reads error.code '\n + 'and ignores the rest. The codes are the server error class names listed under errors with '\n + 'surface \"rest\", not the six clientProofV1 refusal codes — those reach only proven calls',\n pathTemplate:\n 'a {name} segment is a path parameter the client substitutes before signing or sending; '\n + '{provider} is the social provider id (google, apple, kakao, naver)',\n policy:\n 'rate limits and other route policies are server posture, not contract surface: this bundle '\n + 'states wire shapes only',\n },\n canonicalJson: {\n algorithm: 'SPFN-CANON-JSON-1',\n objectKeyOrder: 'ascending by UTF-8 byte sequence',\n whitespace: 'none',\n numbers: 'signed 64-bit integers only; a fractional or non-finite number is a canonicalization error',\n stringEscapes:\n 'quotation mark and reverse solidus escaped; C0 controls use \\\\b \\\\f \\\\n \\\\r \\\\t where defined '\n + 'and \\\\u00XX otherwise; every other scalar is emitted literally',\n encoding: 'UTF-8',\n },\n clientProofV1: {\n profile: CLIENT_PROOF_PROFILE,\n proofInput: {\n algorithm: 'SPFN-PROOF-INPUT-1',\n separator: PROOF_INPUT_SEPARATOR,\n fields: [...PROOF_INPUT_FIELDS],\n fieldRules:\n 'no field value may contain a C0 control character; a value that does is a proof-input error, '\n + 'because the separator would otherwise be ambiguous',\n bodySha256:\n 'lowercase base16 SHA-256 of the canonical JSON request body; the literal string of 64 zero '\n + 'characters when an operation has no body',\n },\n digest: 'SHA-256',\n signature: {\n algorithm: 'ECDSA P-256 with SHA-256',\n encoding: 'raw r||s, two 32-byte big-endian integers, 64 bytes total, base16-lower (128 hex characters)',\n derRule:\n 'a DER-encoded signature is rejected on the wire; a platform signer that emits DER converts to '\n + 'raw r||s before sending',\n lowS:\n 'low-S normalization is not required; uniqueness is owned by the nonce and replay window, so '\n + 'signature malleability cannot replay a request',\n publicKey: 'SPKI DER, base64; x-spfn-key-id names a registered public key',\n },\n proofEncoding: 'base16-lower',\n replayWindowMillis: DEFAULT_REPLAY_WINDOW_MILLIS,\n clientIdRule:\n \"clientId identifies the key owner; the REST surface refuses a proof whose clientId is not the key's \"\n + 'owner id, with the same PROOF_INVALID a failed signature answers',\n replayRule:\n 'a (clientId, nonce) pair is accepted at most once inside the replay window; a repeat is PROOF_REPLAYED',\n revocationRule:\n 'a revoked keyId is rejected before the proof is verified; the outcome is SESSION_REVOKED and never '\n + 'PROOF_INVALID, so revocation is not inferable from a proof failure',\n admissionOrder: ['revocation', 'session', 'expiry', 'replay', 'proof'],\n nonceRule: 'a nonce is spent only when the request is admitted; a refused request leaves it unused',\n },\n wireMapping: {\n requestContentType: CLIENT_PROOF_CONTENT_TYPE,\n headers: { ...CLIENT_PROOF_HEADERS },\n headerOrder: Object.keys(CLIENT_PROOF_HEADERS),\n contentTypeRule:\n 'the content-type header is present exactly when the request carries a body, and the body is always '\n + 'the canonical JSON of the request type',\n sessionRule: `requiresSession operations carry ${CLIENT_PROOF_HEADERS.session}; the handshake never does`,\n clientIdentity: {\n headers: { ...CLIENT_IDENTITY_HEADERS },\n kinds: [...CLIENT_KINDS],\n appliesTo:\n 'every operation, proven or not — enrollment and login are where a stale client is met first, '\n + 'and they carry no proof',\n kindRule:\n 'ios and android ship independently of the server and state the contract version they were '\n + 'generated from; web does not, because a browser bundle is deployed with the server that '\n + 'serves it and has no second version to reconcile',\n versionRule:\n 'the client version is the client\\'s own release — a store version for an app, a build for a '\n + 'browser bundle. It is unauthenticated and nothing is authorized by it',\n refusalRule:\n 'an ios or android client that states no contract version, or one outside the range in the '\n + 'response headers, is refused CONTRACT_UNSUPPORTED; a request naming no kind is not a '\n + 'deployed client and passes',\n proofRule:\n 'none of these headers enters the proof input: they are diagnostic, and PROOF_INPUT_FIELDS is '\n + 'unchanged from 0.5.0',\n },\n serverAnnouncement: {\n headers: { ...SERVER_CONTRACT_HEADERS },\n appliesTo: 'every response, including a refusal',\n rule:\n 'the server states the contract version it serves and the range it accepts. It states no more '\n + 'than that: comparing those against its own version and deciding what a user should be told '\n + 'is the client\\'s judgment, made in the client',\n },\n },\n compatibilityPolicy: {\n policy: 'allOrNothing',\n rule:\n 'one contract version is this whole surface\\'s pass or refusal. Partial compatibility in an auth '\n + 'primitive would mean admitting a client that agrees about some of the admission sequence and '\n + 'not the rest',\n contrast:\n 'an app contract generated from SPFN routes uses perOperation instead, where availability is '\n + 'recorded per operation and the verdict narrows to the operations a client actually calls. The '\n + 'two share this bundle format, so the policy is stated rather than inferred',\n availability:\n 'the since, deprecatedIn and removedIn fields on each operation, described under '\n + 'operationAvailability, are recorded here as well. Under allOrNothing they are descriptive: '\n + 'they are history, not a verdict input. Recording them regardless is what lets a deprecation '\n + 'be announced at all, and is the same shape a perOperation contract decides from',\n },\n typeGrammar: {\n scalars: ['string', 'integer', 'boolean'],\n decimal:\n 'decimal<scale>, where scale is an integer from 1 to 18. The value on the wire is an integer and '\n + 'what it means is that integer divided by 10 to the scale, so decimal<2> carries 1999 for 19.99. '\n + 'There is no floating-point scalar: canonical JSON admits signed 64-bit integers only and treats '\n + 'a fraction as an error. Scale 0 is integer written the long way and is not a valid scale, and 18 '\n + 'is the ceiling because 10^18 is the largest power of ten a signed 64-bit integer holds — above '\n + 'it no integer part is left to carry. This is the only decimal spelling.',\n decimalScaleRule:\n 'the scale is part of the type. Changing it is a breaking change and takes a version bump, and the '\n + 'field is renamed to carry its new unit rather than be remeasured under the same name — the same '\n + 'reason every moment in this contract is named AtMillis. A consumer that kept reading the old '\n + 'name would otherwise decode the same field at a scale nobody told it had moved.',\n decimalGeneratorRule:\n 'a generator emits a decimal type — Swift Decimal, Kotlin BigDecimal — and never a binary float. A '\n + 'value finer than the declared scale is rejected at encoding time and never rounded: rounding '\n + 'would let the client decide what a value the server declared exactly is worth, and it would do '\n + 'so silently.',\n array: 'array<T>, where T is itself a field type. This is the only array spelling.',\n map:\n 'map<string,T>, where T is itself a field type. The key is always string because JSON has no other '\n + 'key type. This is the only map spelling.',\n named: 'any other value names one of the types or enums below',\n enumRule:\n 'a name listed in \"enums\" is a set of string values rather than an object: its declaration carries '\n + 'values instead of fields. The values are the ones the server accepts and sends now; no set is '\n + 'promised to stay as it is, since a value can be added and one can be withdrawn for a weakness '\n + 'found later. What a consumer does with a value outside the set is the consumer\\'s decision',\n dateConvention:\n 'there is no date type. A moment in time is an integer of milliseconds since the Unix epoch and its '\n + 'field name ends in AtMillis — issuedAtMillis, expiresAtMillis, createdAtMillis. A second '\n + 'representation would leave a consumer choosing between two spellings of the same value.',\n dateConventionExceptions: 'none — every moment in this contract is an AtMillis integer',\n rule:\n 'a field type outside this grammar is a contract error, not something to guess at: a consumer that '\n + 'does not recognise a container or decimal spelling reads it as a type name and fails at compile '\n + 'time',\n },\n types: CONTRACT_TYPES.map((type) => ({\n name: type.name,\n fields: type.fields.map((field) => ({ ...field })),\n })),\n enums: CONTRACT_ENUMS.map((declaration) => ({\n name: declaration.name,\n values: [...declaration.values],\n })),\n operations: [\n ...CORE_PREREQUISITE_OPERATIONS,\n ...CONTRACT_OPERATIONS,\n ...AUTH_SURFACE_OPERATIONS,\n ].map((operation) => ({ ...operation })),\n errorEnvelope: {\n shape: '{\"error\":{\"code\":<string>,\"message\":<string>,\"requestId\":<string>}}',\n additionalFields:\n 'the body carries further top-level fields beside the error object — __type, message, and the '\n + 'error class\\'s own public fields. Only error.code classifies the failure',\n unlistedCodes:\n 'the server sends codes this list does not carry. Only the operations enumerated here have had '\n + 'their failure paths listed one by one, and every code is a server error class name rather '\n + 'than a value minted for this contract',\n },\n errors: [\n ...CLIENT_PROOF_ERROR_CODES.map((code) => ({\n code,\n httpStatus: HTTP_STATUS[code],\n retryable: RETRYABLE,\n summary: ERROR_SUMMARIES[code],\n surface: 'clientProofV1',\n })),\n ...REST_SURFACE_ERRORS.map((error) => ({ ...error, surface: 'rest' })),\n ],\n notes: [\n 'This bundle contains no secret, no real key and no production endpoint. Paths are shapes, not deployed routes.',\n 'It is generated output. Edit packages/auth/src/server/client-proof and re-run the export; never edit this file.',\n 'The single authority for this contract is SPFN primitives.',\n ],\n };\n}\n\n/**\n * No major in the filename while the line is 0.x: under 0.x the minor is what\n * breaks, so `v0` would name nothing useful. The version lives in the bundle\n * and the pin is the digest.\n */\nexport const BUNDLE_FILENAME = 'spfn-mobile-contract.json';\nexport const PROVENANCE_FILENAME = 'upstream-provenance.json';\nexport const REPOSITORY = 'git.superfunction.xyz/superfunction/primitives';\nexport const BUNDLE_REPO_PATH = `contracts/mobile/${BUNDLE_FILENAME}`;\n\n/**\n * The evidence spfn-mobile's validator requires before a lock may claim an\n * upstream export.\n *\n * `source.commit` is absent by construction: a file cannot carry the SHA of the\n * commit that contains it. The exporter states everything else and the consumer\n * records which commit it read.\n */\nexport function buildExportProvenance(bundleSha256: string): Record<string, unknown>\n{\n return {\n evidenceVersion: 1,\n origin: EXPORT_ORIGIN,\n exportedByUpstreamCI: true,\n exporterVersion: EXPORTER_VERSION,\n statement:\n 'This contract bundle was generated from the route and contract definitions in SPFN primitives '\n + '(packages/auth/src/server/client-proof) by packages/auth/scripts/export-mobile-contract.ts. '\n + 'It was not transcribed from any consumer.',\n source: {\n repository: REPOSITORY,\n bundlePath: BUNDLE_REPO_PATH,\n commit: 'RECORDED_BY_CONSUMER',\n commitRule:\n 'The consumer sets its own lock source.commit to the exact primitives commit it read this '\n + 'bundle from. A file cannot carry its own commit SHA.',\n },\n contract: {\n name: CONTRACT_NAME,\n version: CONTRACT_VERSION,\n major: CONTRACT_MAJOR,\n supportedRange: CONTRACT_SUPPORTED_RANGE,\n bundleSha256,\n },\n verification: {\n digest: `shasum -a 256 ${BUNDLE_REPO_PATH}`,\n regenerate: 'pnpm --filter @spfn/auth export:mobile-contract',\n enforcedBy: [\n 'packages/auth/src/server/client-proof/__tests__/contract-export.test.ts',\n '.github/workflows/verify-mobile-contract.yml',\n ],\n rule:\n 'The committed bundle must be byte-identical to what the exporter produces. The test above '\n + 'regenerates and compares, so an edited bundle fails the suite rather than shipping.',\n },\n notes: [\n 'The bundle carries no secret, no real key and no production endpoint.',\n 'A published contract version and digest are never modified. A mistake becomes a new version.',\n ],\n };\n}\n\n/**\n * The bundle as the bytes that get committed and digested.\n *\n * A value short enough to fit on one line stays on one line: it keeps field\n * declarations and short lists readable, and it is what makes the emitted text\n * stable across runs. Everything else is indented two spaces.\n */\nexport function serializeMobileContractBundle(bundle: MobileContractBundle): string\n{\n return `${render(bundle, 0)}\\n`;\n}\n\n/** Both files of the export, and the digest the consumer pins. */\nexport function renderMobileContractExport(): { bundle: string; provenance: string; bundleSha256: string }\n{\n const bundle = serializeMobileContractBundle(buildMobileContractBundle());\n const bundleSha256 = createHash('sha256').update(bundle, 'utf8').digest('hex');\n const provenance = `${JSON.stringify(buildExportProvenance(bundleSha256), null, 2)}\\n`;\n\n return { bundle, provenance, bundleSha256 };\n}\n\nconst MAX_INLINE_WIDTH = 100;\n\nfunction render(value: unknown, depth: number): string\n{\n const inline = JSON.stringify(value);\n if (inline === undefined)\n {\n throw new Error('the contract bundle carries a value JSON cannot represent');\n }\n const pad = ' '.repeat(depth);\n if (inline.length + pad.length <= MAX_INLINE_WIDTH || typeof value !== 'object' || value === null)\n {\n return spaced(inline);\n }\n\n const inner = ' '.repeat(depth + 1);\n if (Array.isArray(value))\n {\n const items = value.map((item) => `${inner}${render(item, depth + 1)}`);\n\n return `[\\n${items.join(',\\n')}\\n${pad}]`;\n }\n\n const members = Object.entries(value as Record<string, unknown>)\n .map(([key, member]) => `${inner}${JSON.stringify(key)}: ${render(member, depth + 1)}`);\n\n return `{\\n${members.join(',\\n')}\\n${pad}}`;\n}\n\n/** `{\"a\":1}` → `{ \"a\": 1 }` — the inline form used inside the indented one. */\nfunction spaced(inline: string): string\n{\n if (!inline.startsWith('{') && !inline.startsWith('['))\n {\n return inline;\n }\n let out = '';\n let inString = false;\n let escaped = false;\n for (const ch of inline)\n {\n if (escaped)\n {\n out += ch;\n escaped = false;\n continue;\n }\n if (ch === '\\\\' && inString)\n {\n out += ch;\n escaped = true;\n continue;\n }\n if (ch === '\"')\n {\n inString = !inString;\n out += ch;\n continue;\n }\n if (inString)\n {\n out += ch;\n continue;\n }\n out += separatorFor(ch);\n }\n\n return out;\n}\n\nfunction separatorFor(ch: string): string\n{\n if (ch === ':' || ch === ',')\n {\n return `${ch} `;\n }\n if (ch === '{')\n {\n return '{ ';\n }\n if (ch === '}')\n {\n return ' }';\n }\n\n return ch;\n}\n","/**\n * @spfn/auth - Shared Types\n *\n * Common types and constants used across the auth package\n */\n\n/**\n * Supported JWT signature algorithms\n *\n * - ES256: ECDSA with P-256 and SHA-256 (recommended, smaller keys)\n * - RS256: RSA with SHA-256 (fallback, larger keys)\n */\nexport const KEY_ALGORITHM = ['ES256', 'RS256'] as const;\n\n/**\n * Key algorithm type derived from the const array\n */\nexport type KeyAlgorithmType = typeof KEY_ALGORITHM[number];\n\n/**\n * Where a registered key lives, as the client declares it.\n *\n * Only for telling one entry apart from another in the key list — nothing is\n * authorized or refused by it, so a client that lies gains nothing. Stored via\n * `enumText`, so adding a value here needs no migration.\n */\nexport const KEY_PLATFORM = ['ios', 'android', 'web', 'desktop'] as const;\n\n/**\n * Key platform type derived from the const array\n */\nexport type KeyPlatformType = typeof KEY_PLATFORM[number];\n\n/** Longest device label accepted at registration, and what the list returns. */\nexport const KEY_DEVICE_NAME_MAX_LENGTH = 64;\n\n/**\n * Invitation status enum values\n * Single source of truth for all invitation statuses\n */\nexport const INVITATION_STATUSES = ['pending', 'accepted', 'expired', 'cancelled'] as const;\n\n/**\n * Invitation status type derived from the const array\n */\nexport type InvitationStatus = typeof INVITATION_STATUSES[number];\n\n/**\n * User status enum values\n * Single source of truth for all user statuses\n *\n * - active: Normal operation (default)\n * - inactive: Deactivated (user request, dormant)\n * - suspended: Locked (security incident, ToS violation)\n * - pending_deletion: Deletion requested, within the grace period (recoverable)\n * - deleted: Grace period elapsed and the account was purged (anonymize mode only —\n * hard-delete removes the row instead, so this status never appears for it)\n */\nexport const USER_STATUSES = ['active', 'inactive', 'suspended', 'pending_deletion', 'deleted'] as const;\n\n/**\n * User status type derived from the const array\n */\nexport type UserStatus = typeof USER_STATUSES[number];\n\n/**\n * Social provider enum values\n * Single source of truth for supported OAuth providers\n */\nexport const SOCIAL_PROVIDERS = ['google', 'apple', 'github', 'kakao', 'naver', 'superself'] as const;\n\n/**\n * Social provider type derived from the const array\n */\nexport type SocialProvider = typeof SOCIAL_PROVIDERS[number];\n\n/**\n * Account deletion request status enum values\n * Single source of truth for `account_deletion_requests.status`\n *\n * - pending: Awaiting the grace period (or immediate purge)\n * - cancelled: User (or admin) recovered the account before purge\n * - completed: The purge ran (row is kept as an audit record, never deleted)\n */\nexport const ACCOUNT_DELETION_REQUEST_STATUSES = ['pending', 'cancelled', 'completed'] as const;\n\n/**\n * Account deletion request status type derived from the const array\n */\nexport type AccountDeletionRequestStatus = typeof ACCOUNT_DELETION_REQUEST_STATUSES[number];\n\n/**\n * Who initiated an account deletion request\n */\nexport const ACCOUNT_DELETION_REQUESTED_BY = ['self', 'admin'] as const;\n\n/**\n * Account deletion requester type derived from the const array\n */\nexport type AccountDeletionRequestedBy = typeof ACCOUNT_DELETION_REQUESTED_BY[number];\n\n/**\n * Purge strategy enum values\n *\n * - anonymize: Scrub PII, keep the row (status becomes 'deleted') — default\n * - hard-delete: Physically remove the `users` row (cascades to child rows)\n */\nexport const PURGE_STRATEGIES = ['anonymize', 'hard-delete'] as const;\n\n/**\n * Purge strategy type derived from the const array\n */\nexport type PurgeStrategy = typeof PURGE_STRATEGIES[number];\n","/**\n * The header names each end announces itself under.\n *\n * Separated from the logic that reads them so the contract bundle can name them\n * without importing the version comparison, which reads the bundle back. These\n * are declarations and depend on nothing.\n *\n * @module server/client-proof/wire-headers\n */\n\n/** What a client says about itself, one header each. */\nexport const CLIENT_IDENTITY_HEADERS = {\n kind: 'x-spfn-client-kind',\n version: 'x-spfn-client-version',\n contractVersion: 'x-spfn-client-contract-version',\n} as const;\n\n/**\n * What the server says about itself, on every response.\n *\n * Distinct names from the request headers on purpose: a proxy that echoes a\n * request header into the response would otherwise make the client's own\n * version look like the server's.\n */\nexport const SERVER_CONTRACT_HEADERS = {\n version: 'x-spfn-server-contract-version',\n supportedRange: 'x-spfn-supported-contract-range',\n} as const;\n\n/**\n * The client kinds the server distinguishes.\n *\n * `web` is separated from the two app kinds because it carries no contract\n * version: a browser bundle is deployed with the server that serves it, so\n * there is no second version to reconcile.\n */\nexport const CLIENT_KINDS = ['web', 'ios', 'android'] as const;\n\nexport type ClientKind = typeof CLIENT_KINDS[number];\n\n/** A kind that ships independently of the server, so its contract version matters. */\nexport function isAppKind(kind: ClientKind): boolean\n{\n return kind !== 'web';\n}\n","/**\n * What each end announces about itself, and what the server does with it.\n *\n * A client compiled and shipped separately from the server — a mobile app in a\n * store, a browser tab left open for a week — cannot be fixed by redeploying.\n * Until now a mismatch between what that client was built against and what the\n * server serves surfaced as an undecodable body: the app looked broken and\n * nothing said why.\n *\n * Both ends now say what they are. The client names its kind, its own release\n * and the contract version it was generated from; the server answers with the\n * contract version it serves and the range it accepts. Neither statement enters\n * the proof input — this is diagnostic, not something under authentication, and\n * `PROOF_INPUT_FIELDS` is unchanged.\n *\n * The server states facts and refuses what it cannot serve. It does not tell a\n * client to update: comparing its own version against the announced range and\n * deciding what the user should see is the client's judgment, made in the client.\n *\n * @module server/client-proof/wire-version\n */\nimport { CONTRACT_MAJOR, CONTRACT_SUPPORTED_RANGE, CONTRACT_VERSION } from './contract-bundle';\nimport { ClientProofRefusal } from './refusal';\nimport {\n CLIENT_IDENTITY_HEADERS,\n CLIENT_KINDS,\n isAppKind,\n SERVER_CONTRACT_HEADERS,\n type ClientKind,\n} from './wire-headers';\n\nexport {\n CLIENT_IDENTITY_HEADERS,\n CLIENT_KINDS,\n isAppKind,\n SERVER_CONTRACT_HEADERS,\n type ClientKind,\n} from './wire-headers';\n\n/** What one request announced about the client that sent it. */\nexport interface ClientIdentity\n{\n kind: ClientKind;\n\n /** The client's own release — a store version, or a bundle build. */\n version: string | null;\n\n /** The contract version the client was generated from. Never set for `web`. */\n contractVersion: string | null;\n}\n\n/**\n * Reads the identity headers, or null when the kind is absent or unrecognised.\n *\n * Null is not by itself a refusal — a request from something that predates\n * these headers reaches here too. `judgeClientIdentity` decides.\n */\nexport function readClientIdentity(headers: Headers): ClientIdentity | null\n{\n const kind = headers.get(CLIENT_IDENTITY_HEADERS.kind);\n if (kind === null || !isClientKind(kind))\n {\n return null;\n }\n\n return {\n kind,\n version: headers.get(CLIENT_IDENTITY_HEADERS.version),\n contractVersion: headers.get(CLIENT_IDENTITY_HEADERS.contractVersion),\n };\n}\n\nfunction isClientKind(value: string): value is ClientKind\n{\n return (CLIENT_KINDS as readonly string[]).includes(value);\n}\n\n/**\n * Whether the server serves what the client was generated against.\n *\n * Under 0.x the minor carries breaking changes, so a supported client agrees on\n * major and minor. From 1.0.0 the major alone decides. This is the rule\n * `CONTRACT_SUPPORTED_RANGE` spells out; keeping it as a comparison rather than\n * parsing that string leaves one place to change when the line reaches 1.0.0.\n */\nexport function isContractVersionSupported(clientVersion: string): boolean\n{\n const client = parseVersion(clientVersion);\n if (client === null)\n {\n return false;\n }\n const server = parseVersion(CONTRACT_VERSION);\n if (server === null || client.major !== server.major)\n {\n return false;\n }\n\n return CONTRACT_MAJOR > 0 || client.minor === server.minor;\n}\n\nfunction parseVersion(raw: string): { major: number; minor: number } | null\n{\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:[-+].*)?$/.exec(raw);\n if (match === null)\n {\n return null;\n }\n\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\n/**\n * The refusal a request's announced identity earns, or null to let it through.\n *\n * An app kind must state a contract version this server serves. A version it\n * does not serve, and the absence of one, are the same answer: the two ends do\n * not agree on what the contract is, which is what CONTRACT_UNSUPPORTED means.\n * The response carries the server's version and range, so the client can say\n * which way the gap runs.\n *\n * `web` is exempt from the contract check by construction, not by leniency.\n *\n * A request with no recognised kind passes. The check is on what a client says\n * about itself, and a caller that says nothing — a curl, a health probe, a\n * server-to-server call — is not a deployed client this rule is about.\n */\nexport function judgeClientIdentity(identity: ClientIdentity | null): ClientProofRefusal | null\n{\n if (identity === null || !isAppKind(identity.kind))\n {\n return null;\n }\n if (identity.contractVersion === null)\n {\n return ClientProofRefusal.contractVersionMissing();\n }\n if (!isContractVersionSupported(identity.contractVersion))\n {\n return ClientProofRefusal.contractVersionUnsupported();\n }\n\n return null;\n}\n\n/** Writes the server's own announcement onto a response's headers. */\nexport function applyServerContractHeaders(headers: Headers): void\n{\n headers.set(SERVER_CONTRACT_HEADERS.version, CONTRACT_VERSION);\n headers.set(SERVER_CONTRACT_HEADERS.supportedRange, CONTRACT_SUPPORTED_RANGE);\n}\n\n/** The same announcement as a plain object, for a response built from one. */\nexport function serverContractHeaders(): Record<string, string>\n{\n return {\n [SERVER_CONTRACT_HEADERS.version]: CONTRACT_VERSION,\n [SERVER_CONTRACT_HEADERS.supportedRange]: CONTRACT_SUPPORTED_RANGE,\n };\n}\n","/**\n * The mobile-contract dev surface: a fetch-style handler exposing the three\n * dev operations (handshake / echo.send / items.list) plus the `/control`\n * test hooks the spfn-mobile integration suites drive.\n *\n * Framework-free on purpose — `fetch(request) => Response` plugs into\n * `@hono/node-server`'s `serve({ fetch })` or any Web-standard runtime, and\n * the contract needs byte-exact control over bodies and envelopes that a\n * validating router would take away.\n *\n * This is a dev/test surface, not a production deployment target: keys are\n * injected at construction, state is in-memory, and `/control` mutates it.\n *\n * @module server/client-proof/dev-handler\n */\nimport { encodeCanonicalJson, type CanonicalValue } from './canonical-json';\nimport { admitClientProofRequest, type Admission } from './admission';\nimport {\n CONTRACT_OPERATIONS,\n ContractTypeError,\n decodeEchoRequest,\n decodeHandshakeRequest,\n decodeListItemsRequest,\n encodeEchoResponse,\n encodeHandshakeResponse,\n encodeListItemsResponse,\n type ContractItem,\n type ContractOperation,\n type ListItemsRequest,\n} from './contract-types';\nimport { ClientProofRefusal, newHexId } from './refusal';\nimport { ClientProofState, type ClientProofStateOptions, TestClock } from './state';\nimport { handleControlRequest, CONTROL_PREFIX } from './dev-control';\nimport { serverContractHeaders } from './wire-version';\n\n/** Far above any contract request and far below anything worth buffering. */\nconst MAX_BODY_BYTES = 1 << 20;\n\nconst HTTP_OK = 200;\n\n/**\n * The items `items.list` pages through — fixed and small on purpose, matching\n * the spfn-mobile reference catalogue byte for byte so an integration test can\n * assert exact values against either server.\n */\nexport const DEV_CATALOGUE: readonly ContractItem[] = [\n { id: 'item-0001', name: 'alpha', updatedAtMillis: 1_750_000_000_001n },\n { id: 'item-0002', name: 'bravo', updatedAtMillis: 1_750_000_000_002n },\n { id: 'item-0003', name: 'charlie', updatedAtMillis: 1_750_000_000_003n },\n { id: 'item-0004', name: 'delta', updatedAtMillis: 1_750_000_000_004n },\n { id: 'item-0005', name: 'echo', updatedAtMillis: 1_750_000_000_005n },\n];\n\n/** The largest `items.list` page this server will answer with. */\nexport const DEV_MAX_LIMIT = 100n;\n\nexport interface ClientProofDevHandlerOptions extends ClientProofStateOptions\n{\n /**\n * Token the `/control` routes require (header `x-spfn-reference-control`).\n * Generated per construction when omitted; never logged.\n */\n controlToken?: string;\n\n /** Disables the `/control` surface entirely. @default true */\n enableControl?: boolean;\n\n /** One line per request: method, path, status. Nothing a request carried. */\n log?: (line: string) => void;\n}\n\nexport interface ClientProofDevHandler\n{\n fetch(request: Request): Promise<Response>;\n state: ClientProofState;\n controlToken: string;\n}\n\nexport function createClientProofDevHandler(options: ClientProofDevHandlerOptions): ClientProofDevHandler\n{\n const state = new ClientProofState(options);\n const controlToken = options.controlToken ?? newHexId();\n const enableControl = options.enableControl ?? true;\n const log = options.log ?? (() => undefined);\n\n async function dispatch(request: Request): Promise<Response>\n {\n state.recordRequest();\n const url = new URL(request.url);\n\n if (enableControl && url.pathname.startsWith(CONTROL_PREFIX))\n {\n return handleControlRequest(state, controlToken, url.pathname, request);\n }\n\n // A query string is refused by omission: no contract path carries one,\n // and a proof is taken over the path alone.\n const operation = url.search === ''\n ? CONTRACT_OPERATIONS.find((op) => op.path === url.pathname && op.method === request.method)\n : undefined;\n if (operation === undefined)\n {\n return refuse(ClientProofRefusal.unroutable());\n }\n\n const body = await readBodyCapped(request);\n if (body === null)\n {\n return refuse(ClientProofRefusal.bodyTooLarge());\n }\n\n // Before verification, so a request a test is holding open has not\n // spent its nonce by the time the client gives up waiting for it.\n await waitOutHold(url.pathname);\n\n const admission = admitClientProofRequest({\n state,\n headers: request.headers,\n method: operation.method,\n path: operation.path,\n requiresSession: operation.requiresSession,\n body,\n });\n if (!admission.admitted)\n {\n return refuse(admission.refusal);\n }\n\n return apply(operation, admission);\n }\n\n function apply(operation: ContractOperation, admission: Extract<Admission, { admitted: true }>): Response\n {\n let value: CanonicalValue;\n try\n {\n if (operation.id === 'auth.clientProof.handshake')\n {\n const request = decodeHandshakeRequest(admission.value);\n // The proof already binds the header identity to the key that\n // signed it, so a body naming a different client is a request\n // whose two halves disagree about who sent it.\n if (request.clientId !== admission.credentials.clientId\n || request.keyId !== admission.credentials.keyId)\n {\n return refuse(ClientProofRefusal.bodyNotTheDeclaredType());\n }\n const opened = state.openSession(request.clientId, request.keyId);\n value = encodeHandshakeResponse(opened.sessionId, BigInt(opened.expiresAtMillis));\n }\n else if (operation.id === 'echo.send')\n {\n const request = decodeEchoRequest(admission.value);\n value = encodeEchoResponse(request.message, request.sequence, BigInt(state.nowMillis()));\n }\n else\n {\n const listed = listItems(decodeListItemsRequest(admission.value));\n if (listed === null)\n {\n return refuse(ClientProofRefusal.bodyNotTheDeclaredType());\n }\n value = listed;\n }\n }\n catch (error)\n {\n if (error instanceof ContractTypeError)\n {\n return refuse(ClientProofRefusal.bodyNotTheDeclaredType());\n }\n\n return refuse(ClientProofRefusal.unprocessable());\n }\n\n state.recordOperation(operation.id);\n\n return contractResponse(HTTP_OK, encodeCanonicalJson(value));\n }\n\n function refuse(refusal: ClientProofRefusal): Response\n {\n state.recordRefusal();\n\n return contractResponse(refusal.httpStatus, refusal.envelopeBytes(newHexId()));\n }\n\n async function waitOutHold(path: string): Promise<void>\n {\n const millis = state.takeHoldMillis(path);\n if (millis > 0)\n {\n await new Promise((resolve) => setTimeout(resolve, millis));\n }\n }\n\n return {\n state,\n controlToken,\n fetch: async (request: Request): Promise<Response> =>\n {\n try\n {\n const response = await dispatch(request);\n log(`${request.method} ${new URL(request.url).pathname} -> ${response.status}`);\n\n return response;\n }\n catch\n {\n // A contract answer rather than a stack trace: an exception\n // message can quote the request that produced it.\n return refuse(ClientProofRefusal.unprocessable());\n }\n },\n };\n}\n\n/**\n * One page of the catalogue, or null when the request is not one this\n * contract describes. An unknown cursor and a limit outside 1…MAX are refused\n * rather than clamped — a server that quietly repaired a request would hide\n * the client bug that produced it.\n */\nfunction listItems(request: ListItemsRequest): CanonicalValue | null\n{\n if (request.limit < 1n || request.limit > DEV_MAX_LIMIT)\n {\n return null;\n }\n let start = 0;\n if (request.cursor !== undefined)\n {\n const index = DEV_CATALOGUE.findIndex((item) => item.id === request.cursor);\n if (index < 0)\n {\n return null;\n }\n start = index + 1;\n }\n const end = Math.min(DEV_CATALOGUE.length, start + Number(request.limit));\n const page = DEV_CATALOGUE.slice(start, end);\n // Present only when a further page exists, so \"nextCursor is absent\" is a\n // fact about the data rather than a value the client has to interpret.\n const nextCursor = end < DEV_CATALOGUE.length && page.length > 0 ? page[page.length - 1].id : null;\n\n return encodeListItemsResponse([...page], nextCursor);\n}\n\nfunction contractResponse(status: number, body: Uint8Array): Response\n{\n return new Response(toArrayBuffer(body), {\n status,\n headers: { 'content-type': 'application/json', ...serverContractHeaders() },\n });\n}\n\n/** The body, or null when it is larger than this server will read. */\nasync function readBodyCapped(request: Request): Promise<Uint8Array | null>\n{\n if (request.body === null)\n {\n return new Uint8Array(0);\n }\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;)\n {\n const { done, value } = await reader.read();\n if (done)\n {\n break;\n }\n total += value.length;\n if (total > MAX_BODY_BYTES)\n {\n await reader.cancel();\n\n return null;\n }\n chunks.push(value);\n }\n const body = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks)\n {\n body.set(chunk, offset);\n offset += chunk.length;\n }\n\n return body;\n}\n\nfunction toArrayBuffer(bytes: Uint8Array): ArrayBuffer\n{\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nexport { TestClock };\n","/**\n * One place turns a clientProofV1 refusal into a response.\n *\n * A proven call is answered by a generated SDK that classifies a failure by\n * `error.code` alone and refuses a code it does not know. So a refusal must\n * leave this server as the contract's own envelope — the canonical bytes of\n * `{\"error\":{\"code\",\"message\",\"requestId\"}}` carrying one of the six refusal\n * codes — and nothing else. Routing a refusal through the generic error\n * handler instead puts the wrapper error class's name in `error.code`\n * (`UnauthorizedError`), which no SDK can classify (#106).\n *\n * Every refusal surface (the guard, the profile middleware) builds its answer\n * here rather than assembling one of its own, so a code path added later\n * cannot reintroduce a body that says something else.\n *\n * hono is imported as types only, so this module adds no runtime dependency.\n *\n * @module server/client-proof/refusal-response\n */\nimport type { Context } from 'hono';\n\nimport { newHexId, type ClientProofRefusal } from './refusal';\nimport { serverContractHeaders } from './wire-version';\n\n/**\n * The canonical contract envelope for one refusal, with the server's contract\n * announcement — a refused client needs the range most.\n */\nexport function clientProofRefusalResponse(c: Context, refusal: ClientProofRefusal): Response\n{\n const bytes = refusal.envelopeBytes(newHexId());\n const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n\n return c.newResponse(buffer, refusal.httpStatus as 401, {\n 'content-type': 'application/json',\n ...serverContractHeaders(),\n });\n}\n","/**\n * Hono middleware adapter for clientProofV1 — the `requiresSession` guard for\n * SPFN servers that mount contract operations as ordinary routes.\n *\n * Runs the full admission sequence over the raw request bytes and, on\n * acceptance, tags the request `clientType: 'mobile'` (the attestation slot\n * PROXY-BACKEND-AUTH-SPEC reserved) and exposes the parsed canonical body and\n * credentials under the `clientProof` context key.\n *\n * hono is imported as types only — the middleware itself is a plain async\n * function, so this module adds no runtime dependency.\n *\n * @module server/client-proof/guard\n */\nimport type { Context, MiddlewareHandler, Next } from 'hono';\n\nimport { admitClientProofRequest, type ClientProofCredentials } from './admission';\nimport type { CanonicalValue } from './canonical-json';\nimport { clientProofRefusalResponse } from './refusal-response';\nimport type { ClientProofState } from './state';\n\n/** What the guard leaves in the context for the route handler. */\nexport interface ClientProofContext\n{\n credentials: ClientProofCredentials;\n\n /** The request body as a canonical value (already byte-verified). */\n value: CanonicalValue;\n}\n\nexport interface ClientProofGuardOptions\n{\n /**\n * The contract path the client signed, when it differs from the mounted\n * path (e.g. behind a stripped ingress prefix). Defaults to the request\n * path.\n */\n contractPath?: string;\n}\n\n/**\n * A guard for operations with `requiresSession: true`.\n *\n * Refusals are answered with the contract envelope and never reach the route.\n */\nexport function createClientProofGuard(\n state: ClientProofState,\n options: ClientProofGuardOptions = {},\n): MiddlewareHandler\n{\n return async (c: Context, next: Next) =>\n {\n const body = new Uint8Array(await c.req.arrayBuffer());\n const admission = admitClientProofRequest({\n state,\n headers: c.req.raw.headers,\n method: c.req.method,\n path: options.contractPath ?? c.req.path,\n requiresSession: true,\n body,\n });\n if (!admission.admitted)\n {\n state.recordRefusal();\n\n return clientProofRefusalResponse(c, admission.refusal);\n }\n c.set('clientType', 'mobile');\n c.set('clientProof', {\n credentials: admission.credentials,\n value: admission.value,\n } satisfies ClientProofContext);\n await next();\n\n return undefined;\n };\n}\n","/**\n * The version announcement, applied to every request rather than to the proven\n * ones.\n *\n * Enrollment and login are the first calls a client makes and they carry no\n * proof — there is no key to sign with yet. A check that lives inside proof\n * admission therefore never sees the client it is meant to catch: an outdated\n * app fails at login, before it reaches anything proven. This runs ahead of all\n * of it.\n *\n * hono is imported as types only, so this module adds no runtime dependency.\n *\n * @module server/client-proof/version-middleware\n */\nimport type { Context, MiddlewareHandler, Next } from 'hono';\n\nimport { newHexId } from './refusal';\nimport { applyServerContractHeaders, judgeClientIdentity, readClientIdentity, type ClientIdentity } from './wire-version';\n\n/** The context key the identity is left under, for a handler that wants it. */\nexport const CLIENT_IDENTITY_CONTEXT_KEY = 'clientIdentity';\n\n/**\n * What the client said about itself on this request, or null.\n *\n * Null covers two cases that behave the same downstream: this middleware is not\n * mounted, and it is mounted but the request named no client kind. Neither is an\n * error — an app that predates the headers is still a working app.\n */\nexport function readContextClientIdentity(c: Context): ClientIdentity | null\n{\n return (c.get(CLIENT_IDENTITY_CONTEXT_KEY) as ClientIdentity | undefined) ?? null;\n}\n\n/**\n * Announces the server's contract version on every response and refuses a\n * client whose own contract version this server does not serve.\n *\n * The announcement goes out either way. A refused client needs it most — the\n * refusal says the two ends disagree, and the range is what says how.\n *\n * Mount this before authentication, not after: the point is to answer a stale\n * client before anything else has a chance to fail confusingly.\n */\nexport function createClientVersionMiddleware(): MiddlewareHandler\n{\n return async (c: Context, next: Next) =>\n {\n const identity = readClientIdentity(c.req.raw.headers);\n const refusal = judgeClientIdentity(identity);\n if (refusal !== null)\n {\n const bytes = refusal.envelopeBytes(newHexId());\n const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n const response = c.newResponse(buffer, refusal.httpStatus as 409, {\n 'content-type': 'application/json',\n });\n applyServerContractHeaders(response.headers);\n\n return response;\n }\n if (identity !== null)\n {\n c.set(CLIENT_IDENTITY_CONTEXT_KEY, identity satisfies ClientIdentity);\n }\n await next();\n applyServerContractHeaders(c.res.headers);\n\n return undefined;\n };\n}\n"],"mappings":"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","names":["text","members","hold","INT64_MIN","INT64_MAX","members","required","optional","createHash","CORE_TIME_OPERATION_ID","HTTP_OK"]}
|