@astrasyncai/verification-gateway 2.4.11 → 2.4.14

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.
Files changed (91) hide show
  1. package/dist/adapter-interface/interface.d.mts +2 -2
  2. package/dist/adapter-interface/interface.d.ts +2 -2
  3. package/dist/adapters/express.d.mts +2 -2
  4. package/dist/adapters/express.d.ts +2 -2
  5. package/dist/adapters/express.js +129 -36
  6. package/dist/adapters/express.js.map +1 -1
  7. package/dist/adapters/express.mjs +129 -36
  8. package/dist/adapters/express.mjs.map +1 -1
  9. package/dist/adapters/mcp.d.mts +26 -4
  10. package/dist/adapters/mcp.d.ts +26 -4
  11. package/dist/adapters/mcp.js +94 -28
  12. package/dist/adapters/mcp.js.map +1 -1
  13. package/dist/adapters/mcp.mjs +94 -28
  14. package/dist/adapters/mcp.mjs.map +1 -1
  15. package/dist/adapters/nextjs.d.mts +2 -2
  16. package/dist/adapters/nextjs.d.ts +2 -2
  17. package/dist/adapters/nextjs.js +75 -29
  18. package/dist/adapters/nextjs.js.map +1 -1
  19. package/dist/adapters/nextjs.mjs +75 -29
  20. package/dist/adapters/nextjs.mjs.map +1 -1
  21. package/dist/adapters/sdk.d.mts +2 -2
  22. package/dist/adapters/sdk.d.ts +2 -2
  23. package/dist/adapters/sdk.js +45 -22
  24. package/dist/adapters/sdk.js.map +1 -1
  25. package/dist/adapters/sdk.mjs +45 -22
  26. package/dist/adapters/sdk.mjs.map +1 -1
  27. package/dist/agent/index.d.mts +2 -2
  28. package/dist/agent/index.d.ts +2 -2
  29. package/dist/agent/index.js +29 -0
  30. package/dist/agent/index.js.map +1 -1
  31. package/dist/agent/index.mjs +29 -0
  32. package/dist/agent/index.mjs.map +1 -1
  33. package/dist/browser/background.js +86 -24
  34. package/dist/browser/background.js.map +1 -1
  35. package/dist/browser/background.mjs +86 -24
  36. package/dist/browser/background.mjs.map +1 -1
  37. package/dist/browser/browser-adapter.d.mts +2 -2
  38. package/dist/browser/browser-adapter.d.ts +2 -2
  39. package/dist/cli/index.d.mts +2 -2
  40. package/dist/cli/index.d.ts +2 -2
  41. package/dist/cursor/cursor-adapter.d.mts +2 -2
  42. package/dist/cursor/cursor-adapter.d.ts +2 -2
  43. package/dist/cursor/extension.d.mts +2 -2
  44. package/dist/cursor/extension.d.ts +2 -2
  45. package/dist/cursor/extension.js +86 -24
  46. package/dist/cursor/extension.js.map +1 -1
  47. package/dist/cursor/extension.mjs +86 -24
  48. package/dist/cursor/extension.mjs.map +1 -1
  49. package/dist/{express-C1ePFB7n.d.ts → express-CrfwoNAR.d.ts} +1 -1
  50. package/dist/{express-4WStX3PV.d.mts → express-ienhAXps.d.mts} +1 -1
  51. package/dist/gateway/gateway.d.mts +2 -2
  52. package/dist/gateway/gateway.d.ts +2 -2
  53. package/dist/gateway/gateway.js +86 -24
  54. package/dist/gateway/gateway.js.map +1 -1
  55. package/dist/gateway/gateway.mjs +86 -24
  56. package/dist/gateway/gateway.mjs.map +1 -1
  57. package/dist/git-trigger/git-hooks.d.mts +2 -2
  58. package/dist/git-trigger/git-hooks.d.ts +2 -2
  59. package/dist/{index-ChPX4WHl.d.mts → index-B5e2IDWU.d.mts} +1 -1
  60. package/dist/{index-CzJMCgEy.d.ts → index-CCdZxvAr.d.ts} +71 -6
  61. package/dist/{index-D8IEntil.d.mts → index-CEg_WG6y.d.mts} +71 -6
  62. package/dist/{index-Cjm-zBeZ.d.ts → index-DC5f8eoQ.d.ts} +1 -1
  63. package/dist/index.d.mts +7 -7
  64. package/dist/index.d.ts +7 -7
  65. package/dist/index.js +344 -73
  66. package/dist/index.js.map +1 -1
  67. package/dist/index.mjs +344 -73
  68. package/dist/index.mjs.map +1 -1
  69. package/dist/local-evaluator/evaluator.d.mts +2 -2
  70. package/dist/local-evaluator/evaluator.d.ts +2 -2
  71. package/dist/local-evaluator/evaluator.js +12 -2
  72. package/dist/local-evaluator/evaluator.js.map +1 -1
  73. package/dist/local-evaluator/evaluator.mjs +12 -2
  74. package/dist/local-evaluator/evaluator.mjs.map +1 -1
  75. package/dist/{nextjs-BIORS__0.d.ts → nextjs-66R1KW8e.d.ts} +1 -1
  76. package/dist/{nextjs-CjzHdaXA.d.mts → nextjs-DSpisQst.d.mts} +1 -1
  77. package/dist/{sdk-Chhz-FcT.d.mts → sdk-5U_CBRpr.d.mts} +1 -1
  78. package/dist/{sdk-CqTEQAc6.d.ts → sdk-Bm8np66n.d.ts} +1 -1
  79. package/dist/transport/index.d.mts +2 -2
  80. package/dist/transport/index.d.ts +2 -2
  81. package/dist/transport/index.js +146 -28
  82. package/dist/transport/index.js.map +1 -1
  83. package/dist/transport/index.mjs +146 -28
  84. package/dist/transport/index.mjs.map +1 -1
  85. package/dist/{types-L15pYd2c.d.mts → types-B3USs-Kx.d.mts} +42 -1
  86. package/dist/{types-L15pYd2c.d.ts → types-B3USs-Kx.d.ts} +42 -1
  87. package/dist/{types-DNK2BgIf.d.mts → types-CgDCUfo8.d.mts} +1 -1
  88. package/dist/{types-DoWIuzfj.d.ts → types-R5N4ET6x.d.ts} +1 -1
  89. package/dist/ui/index.d.mts +1 -1
  90. package/dist/ui/index.d.ts +1 -1
  91. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/transport/http.ts","../../src/transport/a2a.ts","../../src/transport/mcp.ts","../../src/transport/rfc9421.ts","../../src/transport/rfc9421-verify.ts","../../src/transport/vi.ts","../../src/transport/stripe-webhook.ts","../../src/transport/ap2.ts","../../src/transport/mpp.ts","../../src/transport/mpp-verify.ts","../../src/transport/x402.ts","../../src/transport/vi-verify.ts","../../src/transport/registry/visa.ts","../../src/transport/index.ts","../../src/agent/errors.ts","../../src/agent/client.ts","../../src/agent/challenge-handler.ts","../../src/agent/pdlss-formatter.ts","../../src/agent/decision-client.ts"],"sourcesContent":["/**\n * HTTP Transport Adapter\n *\n * Maps AstraSync credentials to/from HTTP headers (X-Astra-* convention).\n */\n\nimport type { AstraSyncCredentials } from '../types';\n\nconst HEADER_PREFIX = 'X-Astra-';\n\n/**\n * Inject AstraSync credentials into HTTP headers.\n */\nexport function setHttpHeaders(\n headers: Record<string, string>,\n credentials: AstraSyncCredentials,\n): Record<string, string> {\n const result = { ...headers };\n\n result[`${HEADER_PREFIX}ID`] = credentials.agentId;\n\n if (credentials.verifyUrl) {\n result[`${HEADER_PREFIX}Verify`] = credentials.verifyUrl;\n }\n\n if (credentials.challengeUrl) {\n result[`${HEADER_PREFIX}Challenge`] = credentials.challengeUrl;\n }\n\n if (credentials.pdlss?.purpose) {\n const purposeValue = credentials.pdlss.purpose.action\n ? `${credentials.pdlss.purpose.category}:${credentials.pdlss.purpose.action}`\n : credentials.pdlss.purpose.category;\n result[`${HEADER_PREFIX}Purpose`] = purposeValue;\n }\n\n if (credentials.pdlss?.duration?.maxSessionDuration) {\n result[`${HEADER_PREFIX}Duration`] = String(credentials.pdlss.duration.maxSessionDuration);\n }\n\n if (credentials.pdlss?.scope?.jurisdiction) {\n result[`${HEADER_PREFIX}Scope`] = credentials.pdlss.scope.jurisdiction;\n }\n\n return result;\n}\n\n/**\n * Extract AstraSync credentials from HTTP headers.\n */\nexport function extractHttpCredentials(\n headers: Record<string, string | string[] | undefined>,\n): AstraSyncCredentials | null {\n const getValue = (key: string): string | undefined => {\n const v = headers[key] ?? headers[key.toLowerCase()];\n return Array.isArray(v) ? v[0] : v;\n };\n\n const agentId = getValue(`${HEADER_PREFIX}ID`) ?? getValue('x-astra-id');\n if (!agentId) return null;\n\n const credentials: AstraSyncCredentials = { agentId };\n\n const verifyUrl = getValue(`${HEADER_PREFIX}Verify`) ?? getValue('x-astra-verify');\n if (verifyUrl) credentials.verifyUrl = verifyUrl;\n\n const challengeUrl = getValue(`${HEADER_PREFIX}Challenge`) ?? getValue('x-astra-challenge');\n if (challengeUrl) credentials.challengeUrl = challengeUrl;\n\n const purpose = getValue(`${HEADER_PREFIX}Purpose`) ?? getValue('x-astra-purpose');\n if (purpose) {\n const [category, action] = purpose.split(':');\n credentials.pdlss = {\n ...credentials.pdlss,\n purpose: { category, action },\n };\n }\n\n const duration = getValue(`${HEADER_PREFIX}Duration`) ?? getValue('x-astra-duration');\n if (duration) {\n credentials.pdlss = {\n ...credentials.pdlss,\n duration: { maxSessionDuration: parseInt(duration, 10) },\n };\n }\n\n const scope = getValue(`${HEADER_PREFIX}Scope`) ?? getValue('x-astra-scope');\n if (scope) {\n credentials.pdlss = {\n ...credentials.pdlss,\n scope: { jurisdiction: scope },\n };\n }\n\n return credentials;\n}\n","/**\n * A2A (Agent-to-Agent) Transport Adapter\n *\n * Maps AstraSync credentials to/from A2A task metadata.astrasync block.\n */\n\nimport type { AstraSyncCredentials } from '../types';\n\ninterface A2ATask {\n metadata?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\ninterface AstraSyncMetadata {\n agentId: string;\n verifyUrl?: string;\n challengeUrl?: string;\n purpose?: { category: string; action?: string };\n duration?: { maxSessionDuration?: number };\n scope?: { jurisdiction?: string };\n}\n\n/**\n * Add AstraSync credentials to an A2A task's metadata block.\n */\nexport function setA2AMetadata(\n task: A2ATask,\n credentials: AstraSyncCredentials,\n): A2ATask {\n const astrasync: AstraSyncMetadata = {\n agentId: credentials.agentId,\n };\n\n if (credentials.verifyUrl) astrasync.verifyUrl = credentials.verifyUrl;\n if (credentials.challengeUrl) astrasync.challengeUrl = credentials.challengeUrl;\n if (credentials.pdlss?.purpose) astrasync.purpose = credentials.pdlss.purpose;\n if (credentials.pdlss?.duration) astrasync.duration = credentials.pdlss.duration;\n if (credentials.pdlss?.scope) astrasync.scope = credentials.pdlss.scope;\n\n return {\n ...task,\n metadata: {\n ...task.metadata,\n astrasync,\n },\n };\n}\n\n/**\n * Extract AstraSync credentials from an A2A task's metadata block.\n */\nexport function extractA2ACredentials(task: A2ATask): AstraSyncCredentials | null {\n const meta = task.metadata?.astrasync as AstraSyncMetadata | undefined;\n if (!meta?.agentId) return null;\n\n const credentials: AstraSyncCredentials = {\n agentId: meta.agentId,\n };\n\n if (meta.verifyUrl) credentials.verifyUrl = meta.verifyUrl;\n if (meta.challengeUrl) credentials.challengeUrl = meta.challengeUrl;\n\n if (meta.purpose || meta.duration || meta.scope) {\n credentials.pdlss = {};\n if (meta.purpose) credentials.pdlss.purpose = meta.purpose;\n if (meta.duration) credentials.pdlss.duration = meta.duration;\n if (meta.scope) credentials.pdlss.scope = meta.scope;\n }\n\n return credentials;\n}\n","/**\n * MCP (Model Context Protocol) Transport Adapter\n *\n * Maps AstraSync credentials to/from MCP params._meta.astrasync block.\n */\n\nimport type { AstraSyncCredentials } from '../types';\n\ninterface McpParams {\n _meta?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\ninterface AstraSyncMeta {\n agentId: string;\n verifyUrl?: string;\n challengeUrl?: string;\n purpose?: { category: string; action?: string };\n duration?: { maxSessionDuration?: number };\n scope?: { jurisdiction?: string };\n}\n\n/**\n * Add AstraSync credentials to MCP params' _meta block.\n */\nexport function setMcpMeta(\n params: McpParams,\n credentials: AstraSyncCredentials,\n): McpParams {\n const astrasync: AstraSyncMeta = {\n agentId: credentials.agentId,\n };\n\n if (credentials.verifyUrl) astrasync.verifyUrl = credentials.verifyUrl;\n if (credentials.challengeUrl) astrasync.challengeUrl = credentials.challengeUrl;\n if (credentials.pdlss?.purpose) astrasync.purpose = credentials.pdlss.purpose;\n if (credentials.pdlss?.duration) astrasync.duration = credentials.pdlss.duration;\n if (credentials.pdlss?.scope) astrasync.scope = credentials.pdlss.scope;\n\n return {\n ...params,\n _meta: {\n ...params._meta,\n astrasync,\n },\n };\n}\n\n/**\n * Extract AstraSync credentials from MCP params' _meta block.\n */\nexport function extractMcpCredentials(params: McpParams): AstraSyncCredentials | null {\n const meta = params._meta?.astrasync as AstraSyncMeta | undefined;\n if (!meta?.agentId) return null;\n\n const credentials: AstraSyncCredentials = {\n agentId: meta.agentId,\n };\n\n if (meta.verifyUrl) credentials.verifyUrl = meta.verifyUrl;\n if (meta.challengeUrl) credentials.challengeUrl = meta.challengeUrl;\n\n if (meta.purpose || meta.duration || meta.scope) {\n credentials.pdlss = {};\n if (meta.purpose) credentials.pdlss.purpose = meta.purpose;\n if (meta.duration) credentials.pdlss.duration = meta.duration;\n if (meta.scope) credentials.pdlss.scope = meta.scope;\n }\n\n return credentials;\n}\n","/**\n * RFC 9421 HTTP Message Signatures parser.\n *\n * Wraps `structured-headers` (transitive dep of http-message-signatures) to\n * parse the Signature-Input and Signature Dictionary headers per RFC 9421 §2.\n *\n * Produces structured metadata (kid, algorithm, covered components, tag,\n * created/expires/nonce, signature bytes) without verifying the signature —\n * verification lives in rfc9421-verify.ts.\n *\n * Shared by:\n * - Agent Pay (Mastercard) — kid resolves via Mastercard Agent Registry\n * - TAP (Visa) — kid resolves via Visa JWKS\n * - Web Bot Auth (generic transport substrate) — kid resolves via\n * /.well-known/http-message-signatures-directory\n */\n\nimport { parseDictionary } from 'structured-headers';\n\nexport interface RFC9421SignatureParams {\n /** The label identifying the signature in the Dictionary header (e.g. \"sig1\"). */\n label: string;\n /** Key ID used to look up the verifying key in the relevant registry. */\n kid: string;\n /** Algorithm declared in the Signature-Input params (e.g. \"ecdsa-p256-sha256\", \"ed25519\"). */\n alg?: string;\n /** Covered components, in order, per RFC 9421 §2.1. */\n covered: string[];\n /** Base64url-encoded signature bytes extracted from the paired Signature header. */\n signatureBase64: string;\n /** Unix seconds when the signature was created. */\n created?: number;\n /** Unix seconds when the signature expires. */\n expires?: number;\n /** Nonce (opaque string) for replay protection. */\n nonce?: string;\n /** Tag parameter. For Agent Pay/TAP this is \"browse\" or \"purchase\"; undefined otherwise. */\n tag?: 'browse' | 'purchase' | string;\n}\n\nexport interface ParsedRFC9421 {\n signatures: RFC9421SignatureParams[];\n}\n\n/**\n * Parse the RFC 9421 Signature-Input and Signature headers from a request or response.\n * Returns all signatures present (a single message may carry multiple labelled signatures).\n *\n * Returns null if either header is missing or malformed.\n */\nexport function parseRFC9421(\n headers: Record<string, string | string[] | undefined>\n): ParsedRFC9421 | null {\n const sigInput = readHeader(headers, 'signature-input');\n const sig = readHeader(headers, 'signature');\n if (!sigInput || !sig) return null;\n\n let inputDict;\n let sigDict;\n try {\n inputDict = parseDictionary(sigInput);\n sigDict = parseDictionary(sig);\n } catch {\n return null;\n }\n\n const signatures: RFC9421SignatureParams[] = [];\n\n for (const [label, entry] of inputDict) {\n // entry.value is the inner list of covered components; entry[1] is the params Map.\n const innerList = Array.isArray(entry)\n ? entry[0]\n : (entry as { value?: unknown; params?: unknown }).value;\n const params = Array.isArray(entry)\n ? entry[1]\n : (entry as { value?: unknown; params?: unknown }).params;\n if (!Array.isArray(innerList) || !params) continue;\n\n const covered: string[] = [];\n for (const item of innerList as Array<[unknown, Map<string, unknown>]>) {\n const [bare] = Array.isArray(item) ? item : [item];\n if (typeof bare === 'string') covered.push(bare);\n else if (bare && typeof bare === 'object' && 'toString' in bare) covered.push(String(bare));\n }\n\n const paramsMap = params as Map<string, unknown>;\n const kid = coerceString(paramsMap.get('keyid'));\n if (!kid) continue;\n\n const sigEntry = sigDict.get(label);\n if (!sigEntry) continue;\n\n const sigBare = Array.isArray(sigEntry) ? sigEntry[0] : (sigEntry as { value?: unknown }).value;\n const signatureBase64 = extractBase64(sigBare);\n if (!signatureBase64) continue;\n\n signatures.push({\n label,\n kid,\n alg: coerceString(paramsMap.get('alg')),\n covered,\n signatureBase64,\n created: coerceNumber(paramsMap.get('created')),\n expires: coerceNumber(paramsMap.get('expires')),\n nonce: coerceString(paramsMap.get('nonce')),\n tag: coerceString(paramsMap.get('tag')),\n });\n }\n\n if (signatures.length === 0) return null;\n return { signatures };\n}\n\nfunction readHeader(\n headers: Record<string, string | string[] | undefined>,\n name: string\n): string | null {\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === name) {\n const raw = headers[key];\n if (typeof raw === 'string') return raw;\n if (Array.isArray(raw)) return raw.join(', ');\n return null;\n }\n }\n return null;\n}\n\nfunction coerceString(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (value == null) return undefined;\n if (typeof value === 'object' && 'toString' in (value as object)) {\n const s = String(value);\n return s.length > 0 ? s : undefined;\n }\n return undefined;\n}\n\nfunction coerceNumber(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n if (typeof value === 'bigint') return Number(value);\n return undefined;\n}\n\nfunction extractBase64(value: unknown): string | null {\n if (value instanceof Uint8Array) return bufferToBase64(value);\n if (value instanceof ArrayBuffer) return bufferToBase64(new Uint8Array(value));\n if (ArrayBuffer.isView(value)) {\n const v = value as ArrayBufferView;\n return bufferToBase64(new Uint8Array(v.buffer, v.byteOffset, v.byteLength));\n }\n if (typeof value === 'string') {\n if (value.startsWith(':') && value.endsWith(':')) return value.slice(1, -1);\n return value;\n }\n return null;\n}\n\nfunction bufferToBase64(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64');\n}\n","/**\n * RFC 9421 HTTP Message Signatures verification.\n *\n * Wraps http-message-signatures (dhensby) verifyMessage() with a RegistryResolver\n * hook for kid → JWK lookup. Library handles canonicalization + ES256/EdDSA/\n * HMAC/RSA verification; we supply the key-finding callback and policy around\n * clock skew.\n *\n * Shared by:\n * - Agent Pay (Mastercard) — resolver = createMastercardRegistry\n * - TAP (Visa) — resolver = createVisaRegistry\n * - Web Bot Auth (generic) — resolver = createWebBotAuthRegistry\n */\n\nimport { httpbis, type VerifierFinder, type VerifyingKey } from 'http-message-signatures';\nimport type { JWK } from 'jose';\nimport type { RegistryResolver } from './registry/types';\n\nexport interface RFC9421VerifyRequest {\n method: string;\n url: string;\n headers: Record<string, string | string[]>;\n body?: string;\n}\n\nexport interface RFC9421VerifyOptions {\n resolver: RegistryResolver;\n /** Seconds of tolerance around created/expires. Default 300. */\n clockSkewSec?: number;\n /** Injectable for deterministic tests. */\n now?: () => number;\n}\n\nexport interface RFC9421VerifyResult {\n ok: boolean;\n kid?: string;\n registry?: RegistryResolver['name'];\n algorithm?: string;\n error?: string;\n}\n\nexport async function verifyRFC9421(\n request: RFC9421VerifyRequest,\n options: RFC9421VerifyOptions\n): Promise<RFC9421VerifyResult> {\n const { resolver } = options;\n const tolerance = options.clockSkewSec ?? 300;\n const nowSec = options.now ? options.now() : Math.floor(Date.now() / 1000);\n\n let resolvedKid: string | undefined;\n let resolvedAlg: string | undefined;\n\n const keyLookup: VerifierFinder = async (parameters) => {\n const kid = typeof parameters.keyid === 'string' ? parameters.keyid : undefined;\n if (!kid) return null;\n resolvedKid = kid;\n const alg = typeof parameters.alg === 'string' ? parameters.alg : undefined;\n if (alg) resolvedAlg = alg;\n\n const origin = safeOrigin(request.url);\n const jwk = await resolver.resolve(kid, { origin, algorithm: alg });\n if (!jwk) return null;\n\n // Check clock-skew on this specific signature's created/expires.\n // SignatureParameters may carry Date, number, or ISO string per library.\n const created = toUnixSeconds(parameters.created);\n const expires = toUnixSeconds(parameters.expires);\n if (created !== undefined && Math.abs(nowSec - created) > tolerance) return null;\n if (expires !== undefined && nowSec > expires + tolerance) return null;\n\n return jwkToVerifyingKey(kid, jwk, alg);\n };\n\n try {\n const result = await httpbis.verifyMessage(\n {\n keyLookup,\n },\n normalizeRequest(request)\n );\n if (result === true) {\n return {\n ok: true,\n kid: resolvedKid,\n registry: resolver.name,\n algorithm: resolvedAlg,\n };\n }\n return {\n ok: false,\n kid: resolvedKid,\n registry: resolver.name,\n algorithm: resolvedAlg,\n error: result === false ? 'signature invalid' : 'no signature found',\n };\n } catch (err) {\n return {\n ok: false,\n kid: resolvedKid,\n registry: resolver.name,\n algorithm: resolvedAlg,\n error: err instanceof Error ? err.message : 'verification error',\n };\n }\n}\n\nfunction normalizeRequest(request: RFC9421VerifyRequest): {\n method: string;\n url: string;\n headers: Record<string, string | string[]>;\n} {\n return {\n method: request.method.toUpperCase(),\n url: request.url,\n headers: request.headers,\n };\n}\n\nfunction safeOrigin(url: string): string | undefined {\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\nasync function jwkToVerifyingKey(\n id: string,\n jwk: JWK,\n alg: string | undefined\n): Promise<VerifyingKey> {\n const algorithm = alg ?? inferAlgFromJwk(jwk);\n const { subtle } = await getCrypto();\n const importAlg = webCryptoImportAlgFor(algorithm);\n const verifyAlg = webCryptoAlgFor(algorithm);\n if (!importAlg || !verifyAlg) {\n return {\n id,\n algs: alg ? [alg] : undefined,\n verify: async () => false,\n };\n }\n const key = await subtle.importKey('jwk', jwk as JsonWebKey, importAlg, false, ['verify']);\n\n return {\n id,\n algs: alg ? [alg] : undefined,\n verify: async (data: Buffer, signature: Buffer): Promise<boolean> => {\n try {\n return await subtle.verify(verifyAlg, key, toArrayBuffer(signature), toArrayBuffer(data));\n } catch {\n return false;\n }\n },\n };\n}\n\nfunction inferAlgFromJwk(jwk: JWK): string {\n if (jwk.kty === 'OKP' && jwk.crv === 'Ed25519') return 'ed25519';\n if (jwk.kty === 'EC' && jwk.crv === 'P-256') return 'ecdsa-p256-sha256';\n if (jwk.kty === 'EC' && jwk.crv === 'P-384') return 'ecdsa-p384-sha384';\n if (jwk.kty === 'RSA') return 'rsa-v1_5-sha256';\n return 'ecdsa-p256-sha256';\n}\n\nfunction webCryptoAlgFor(\n rfc9421Alg: string\n): AlgorithmIdentifier | EcdsaParams | RsaPssParams | null {\n switch (rfc9421Alg) {\n case 'ed25519':\n return { name: 'Ed25519' };\n case 'ecdsa-p256-sha256':\n return { name: 'ECDSA', hash: 'SHA-256' };\n case 'ecdsa-p384-sha384':\n return { name: 'ECDSA', hash: 'SHA-384' };\n case 'rsa-v1_5-sha256':\n return { name: 'RSASSA-PKCS1-v1_5' };\n case 'rsa-pss-sha512':\n return { name: 'RSA-PSS', saltLength: 64 };\n default:\n return null;\n }\n}\n\nfunction webCryptoImportAlgFor(\n rfc9421Alg: string\n): AlgorithmIdentifier | EcKeyImportParams | RsaHashedImportParams | null {\n switch (rfc9421Alg) {\n case 'ed25519':\n return { name: 'Ed25519' };\n case 'ecdsa-p256-sha256':\n return { name: 'ECDSA', namedCurve: 'P-256' };\n case 'ecdsa-p384-sha384':\n return { name: 'ECDSA', namedCurve: 'P-384' };\n case 'rsa-v1_5-sha256':\n return { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' };\n case 'rsa-pss-sha512':\n return { name: 'RSA-PSS', hash: 'SHA-512' };\n default:\n return null;\n }\n}\n\nfunction toArrayBuffer(buf: Buffer): ArrayBuffer {\n const out = new ArrayBuffer(buf.byteLength);\n new Uint8Array(out).set(buf);\n return out;\n}\n\nfunction toUnixSeconds(v: unknown): number | undefined {\n if (typeof v === 'number' && Number.isFinite(v)) return v;\n if (v instanceof Date) return Math.floor(v.getTime() / 1000);\n if (typeof v === 'string') {\n const parsed = Date.parse(v);\n if (Number.isFinite(parsed)) return Math.floor(parsed / 1000);\n }\n return undefined;\n}\n\nasync function getCrypto(): Promise<{ subtle: SubtleCrypto }> {\n if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.subtle) {\n return { subtle: globalThis.crypto.subtle };\n }\n // Node fallback\n const nodeCrypto = await import('node:crypto');\n return { subtle: nodeCrypto.webcrypto.subtle as SubtleCrypto };\n}\n","/**\n * VI (Verifiable Intent) SD-JWT extraction.\n *\n * Open-sourced 5 March 2026 by Mastercard + Google (v0.1-draft).\n * VI is a 3-layer SD-JWT chain:\n * L1 — issuer → wallet (credential provider)\n * L2 — user → agent (cnf.jwk binding to L3 agent key)\n * L3 — agent → merchant (payment or checkout mandate, split into L3a / L3b\n * cross-referenced via transaction_id)\n *\n * This module does EXTRACTION ONLY — it decodes SD-JWT structure and pulls\n * out the mandate type, kid, executionMode, 8 constraint types, checkoutHash\n * (constraint type 8), transactionId, and raw layers for later verification.\n *\n * Signature verification lives in vi-verify.ts; this module uses @sd-jwt's\n * sync decoder with a SHA-256 hasher for structural parsing only.\n */\n\nimport { splitSdJwt, decodeSdJwtSync } from '@sd-jwt/decode';\nimport { createHash } from 'node:crypto';\nimport type { VIMandateType } from './purpose-mapping';\n\nexport type { VIMandateType };\nexport type VIExecutionMode = 'Immediate' | 'Autonomous' | 'Both';\n\nexport interface VIAllowedParty {\n id?: string;\n name?: string;\n website?: string;\n}\n\nexport interface VILineItem {\n id?: string;\n acceptableItems?: string[];\n quantity?: number;\n}\n\nexport interface VIPaymentAmount {\n currency?: string;\n min?: number;\n max?: number;\n}\n\nexport interface VIBudgetLimit {\n currency?: string;\n max?: number;\n}\n\nexport interface VIRecurrence {\n frequency?: string;\n startDate?: string;\n endDate?: string;\n maxOccurrences?: number;\n}\n\nexport interface VIConstraints {\n allowedMerchants?: VIAllowedParty[];\n allowedPayees?: VIAllowedParty[];\n lineItems?: VILineItem[];\n paymentAmount?: VIPaymentAmount;\n budgetLimit?: VIBudgetLimit;\n recurrence?: VIRecurrence;\n agentRecurrence?: VIRecurrence;\n}\n\nexport interface VIExtractedClaims {\n mandateType: VIMandateType;\n kid?: string;\n executionMode?: VIExecutionMode;\n credentialProvider?: string;\n constraints: VIConstraints;\n /** VI constraint type 8 — SHA-256 of the paired L2 checkout disclosure. */\n checkoutHash?: string;\n transactionId?: string;\n rawLayers: { l1?: string; l2?: string; l3?: string };\n}\n\n/**\n * Extract VI claims from a compact SD-JWT string.\n *\n * Input shape:\n * <jwt>~<disclosure1>~<disclosure2>~...~<kbJwt?>\n *\n * Returns null if parsing fails at any layer. Does not verify signatures.\n */\nexport function extractVIClaims(sdJwtCompact: string): VIExtractedClaims | null {\n if (!sdJwtCompact || typeof sdJwtCompact !== 'string') return null;\n\n let decoded;\n try {\n decoded = decodeSdJwtSync(sdJwtCompact, sha256Sync);\n } catch {\n return null;\n }\n\n const split = safeSplit(sdJwtCompact);\n\n const payload = (decoded.jwt?.payload ?? {}) as Record<string, unknown>;\n const disclosures = decoded.disclosures ?? [];\n\n // Apply disclosures onto payload to resolve _sd references.\n // Disclosure from @sd-jwt/utils has { key, value, digest() } where digest is\n // a function — we only need key+value here, so narrow via a structural cast.\n const claims = applyDisclosures(\n payload,\n disclosures as unknown as Array<{ key?: string; value?: unknown }>\n );\n\n const mandateType = coerceMandateType(\n claims.mandate_type ?? claims.mandateType ?? payload.mandate_type ?? payload.mandateType\n );\n if (!mandateType) return null;\n\n const kid = coerceString(\n (decoded.jwt?.header as Record<string, unknown> | undefined)?.kid ?? claims.kid ?? payload.kid\n );\n\n const executionMode = coerceExecutionMode(claims.execution_mode ?? claims.executionMode);\n const credentialProvider = coerceString(claims.iss ?? payload.iss);\n\n const constraints = extractConstraints(\n (claims.constraints ?? claims.default_constraints ?? {}) as Record<string, unknown>\n );\n\n const transactionId = coerceString(claims.transaction_id ?? claims.transactionId);\n const checkoutHash = coerceString(\n claims.checkout_hash ??\n claims.conditional_transaction_id ??\n (claims.payment_reference as Record<string, unknown> | undefined)?.checkout_hash\n );\n\n return {\n mandateType,\n kid,\n executionMode,\n credentialProvider,\n constraints,\n checkoutHash,\n transactionId,\n rawLayers: split,\n };\n}\n\nfunction safeSplit(compact: string): { l1?: string; l2?: string; l3?: string } {\n try {\n const { jwt, kbJwt } = splitSdJwt(compact);\n // VI layering maps loosely: the outer JWT is L3 (agent mandate), KB-JWT\n // (if present) is the key-binding proof, and disclosures carry L2/L1 fragments.\n return { l3: jwt, l2: kbJwt };\n } catch {\n return {};\n }\n}\n\nfunction applyDisclosures(\n payload: Record<string, unknown>,\n disclosures: Array<{ key?: string; value?: unknown }>\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...payload };\n for (const d of disclosures) {\n if (d.key && d.value !== undefined && !(d.key in result)) {\n result[d.key] = d.value;\n }\n }\n return result;\n}\n\nfunction extractConstraints(raw: Record<string, unknown>): VIConstraints {\n return {\n allowedMerchants: toAllowedPartyArray(raw.allowed_merchants ?? raw.allowedMerchants),\n allowedPayees: toAllowedPartyArray(raw.allowed_payees ?? raw.allowedPayees),\n lineItems: toLineItemArray(raw.line_items ?? raw.lineItems),\n paymentAmount: toPaymentAmount(raw.payment_amount ?? raw.paymentAmount),\n budgetLimit: toBudgetLimit(raw.budget_limit ?? raw.budgetLimit ?? raw.budget),\n recurrence: toRecurrence(raw.recurrence),\n agentRecurrence: toRecurrence(raw.agent_recurrence ?? raw.agentRecurrence),\n };\n}\n\nfunction toAllowedPartyArray(v: unknown): VIAllowedParty[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out: VIAllowedParty[] = [];\n for (const item of v) {\n if (item && typeof item === 'object') {\n const r = item as Record<string, unknown>;\n out.push({\n id: coerceString(r.id),\n name: coerceString(r.name),\n website: coerceString(r.website),\n });\n }\n }\n return out.length > 0 ? out : undefined;\n}\n\nfunction toLineItemArray(v: unknown): VILineItem[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out: VILineItem[] = [];\n for (const item of v) {\n if (item && typeof item === 'object') {\n const r = item as Record<string, unknown>;\n const acc = r.acceptable_items ?? r.acceptableItems;\n out.push({\n id: coerceString(r.id),\n acceptableItems: Array.isArray(acc)\n ? (acc.filter((a) => typeof a === 'string') as string[])\n : undefined,\n quantity: coerceNumber(r.quantity),\n });\n }\n }\n return out.length > 0 ? out : undefined;\n}\n\nfunction toPaymentAmount(v: unknown): VIPaymentAmount | undefined {\n if (!v || typeof v !== 'object') return undefined;\n const r = v as Record<string, unknown>;\n return {\n currency: coerceString(r.currency),\n min: coerceNumber(r.min),\n max: coerceNumber(r.max),\n };\n}\n\nfunction toBudgetLimit(v: unknown): VIBudgetLimit | undefined {\n if (!v || typeof v !== 'object') return undefined;\n const r = v as Record<string, unknown>;\n return {\n currency: coerceString(r.currency),\n max: coerceNumber(r.max),\n };\n}\n\nfunction toRecurrence(v: unknown): VIRecurrence | undefined {\n if (!v || typeof v !== 'object') return undefined;\n const r = v as Record<string, unknown>;\n return {\n frequency: coerceString(r.frequency),\n startDate: coerceString(r.start_date ?? r.startDate),\n endDate: coerceString(r.end_date ?? r.endDate),\n maxOccurrences: coerceNumber(r.max_occurrences ?? r.maxOccurrences),\n };\n}\n\nfunction coerceMandateType(v: unknown): VIMandateType | null {\n if (v === 'checkout' || v === 'payment' || v === 'checkout.open' || v === 'payment.open') {\n return v;\n }\n return null;\n}\n\nfunction coerceExecutionMode(v: unknown): VIExecutionMode | undefined {\n return v === 'Immediate' || v === 'Autonomous' || v === 'Both' ? v : undefined;\n}\n\nfunction coerceString(v: unknown): string | undefined {\n return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n\nfunction coerceNumber(v: unknown): number | undefined {\n if (typeof v === 'number' && Number.isFinite(v)) return v;\n if (typeof v === 'string') {\n const n = Number(v);\n return Number.isFinite(n) ? n : undefined;\n }\n return undefined;\n}\n\nfunction sha256Sync(data: string | ArrayBuffer): Uint8Array {\n const buf =\n typeof data === 'string' ? Buffer.from(data, 'utf-8') : Buffer.from(new Uint8Array(data));\n const hash = createHash('sha256').update(buf).digest();\n return new Uint8Array(hash.buffer, hash.byteOffset, hash.byteLength);\n}\n","/**\n * Stripe webhook HMAC-SHA256 verifier (inline).\n *\n * Stripe-Signature header format: \"t=TIMESTAMP,v1=HEX_SIGNATURE\"\n * - t: unix seconds when Stripe signed the webhook\n * - v1: HMAC-SHA256(webhook_secret, `${t}.${payload}`) as hex\n *\n * Multiple v1 signatures can coexist during secret rotation; any match wins.\n * Default tolerance on timestamp age: 300s (matches Stripe's own default).\n *\n * Documented at docs.stripe.com — we intentionally inline ~25 LOC rather\n * than pull in the full stripe npm package (MIT but 600KB+ with deps).\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\nexport interface VerifyStripeWebhookResult {\n ok: boolean;\n timestamp?: number;\n error?: string;\n}\n\nexport interface VerifyStripeWebhookOptions {\n toleranceSec?: number;\n /** Injectable for deterministic tests. */\n now?: () => number;\n}\n\nexport function verifyStripeWebhook(\n payload: string,\n signatureHeader: string | undefined,\n secret: string,\n options: VerifyStripeWebhookOptions = {}\n): VerifyStripeWebhookResult {\n if (!signatureHeader) return { ok: false, error: 'missing Stripe-Signature header' };\n if (!secret) return { ok: false, error: 'missing webhook secret' };\n\n const parsed = parseStripeSignature(signatureHeader);\n if (!parsed.timestamp) return { ok: false, error: 'malformed Stripe-Signature (missing t=)' };\n if (parsed.v1Signatures.length === 0) {\n return { ok: false, error: 'malformed Stripe-Signature (no v1=)' };\n }\n\n const tolerance = options.toleranceSec ?? 300;\n const now = options.now ? options.now() : Math.floor(Date.now() / 1000);\n if (Math.abs(now - parsed.timestamp) > tolerance) {\n return {\n ok: false,\n timestamp: parsed.timestamp,\n error: `timestamp outside tolerance (${tolerance}s)`,\n };\n }\n\n const signedPayload = `${parsed.timestamp}.${payload}`;\n const expected = createHmac('sha256', secret).update(signedPayload).digest();\n\n for (const candidateHex of parsed.v1Signatures) {\n const candidate = hexToBuffer(candidateHex);\n if (!candidate) continue;\n if (candidate.length !== expected.length) continue;\n if (timingSafeEqual(candidate, expected)) {\n return { ok: true, timestamp: parsed.timestamp };\n }\n }\n\n return { ok: false, timestamp: parsed.timestamp, error: 'signature mismatch' };\n}\n\ninterface ParsedStripeSignature {\n timestamp: number | null;\n v1Signatures: string[];\n}\n\nfunction parseStripeSignature(header: string): ParsedStripeSignature {\n let timestamp: number | null = null;\n const v1Signatures: string[] = [];\n for (const part of header.split(',')) {\n const [rawKey, rawValue] = part.split('=');\n if (!rawKey || !rawValue) continue;\n const key = rawKey.trim();\n const value = rawValue.trim();\n if (key === 't') {\n const n = Number(value);\n if (Number.isFinite(n)) timestamp = n;\n } else if (key === 'v1') {\n v1Signatures.push(value);\n }\n }\n return { timestamp, v1Signatures };\n}\n\nfunction hexToBuffer(hex: string): Buffer | null {\n if (!/^[0-9a-fA-F]+$/.test(hex) || hex.length % 2 !== 0) return null;\n return Buffer.from(hex, 'hex');\n}\n","/**\n * AP2 (Agent Payments Protocol) mandate extraction.\n *\n * Google-led, launched 3 April 2026 with 60+ partners (Mastercard, PayPal,\n * Coinbase, AmEx, Revolut, UnionPay, ...). AP2 ships three mandate types as\n * SD-JWTs in series:\n * - intent_mandate — user declares intent (amount, merchant category, etc.)\n * - cart_mandate — user approves a cart (specific items, totals)\n * - payment_mandate — authorizes the actual payment rail\n *\n * Mandates are cross-referenced via ids; each is an SD-JWT over ES256 (or\n * equivalent). We decode via @sd-jwt/decode and extract the AP2-specific\n * shape — verification lives in ap2-verify.ts.\n */\n\nimport { decodeSdJwtSync } from '@sd-jwt/decode';\nimport { createHash } from 'node:crypto';\nimport type { AP2MandateType } from './purpose-mapping';\n\nexport type { AP2MandateType };\n\nexport interface AP2PaymentDetailsTotal {\n amount?: { value?: string | number; currency?: string };\n label?: string;\n}\n\nexport interface AP2IntentMandateClaims {\n type: 'intent_mandate';\n agent_id?: string;\n user_id?: string;\n merchant_category?: string;\n allowedMerchantDomains?: string[];\n paymentMethods?: string[];\n expires?: string;\n payment_details_total?: AP2PaymentDetailsTotal;\n raw: Record<string, unknown>;\n}\n\nexport interface AP2CartMandateClaims {\n type: 'cart_mandate';\n agent_id?: string;\n intent_mandate_id?: string;\n merchant_id?: string;\n line_items?: Array<{\n id?: string;\n quantity?: number;\n price?: { value?: string | number; currency?: string };\n }>;\n payment_details_total?: AP2PaymentDetailsTotal;\n expires?: string;\n raw: Record<string, unknown>;\n}\n\nexport interface AP2PaymentMandateClaims {\n type: 'payment_mandate';\n agent_id?: string;\n cart_mandate_id?: string;\n payment_method?: string;\n payment_details_total?: AP2PaymentDetailsTotal;\n credential_provider?: string;\n raw: Record<string, unknown>;\n}\n\nexport type AP2MandateClaims =\n | AP2IntentMandateClaims\n | AP2CartMandateClaims\n | AP2PaymentMandateClaims;\n\nexport interface AP2MandateTriple {\n intent?: AP2IntentMandateClaims;\n cart?: AP2CartMandateClaims;\n payment?: AP2PaymentMandateClaims;\n rawLayers: { intentJwt?: string; cartJwt?: string; paymentJwt?: string };\n}\n\n/**\n * Extract a single AP2 mandate from a compact SD-JWT.\n * Returns null if the SD-JWT is malformed or lacks a recognized type field.\n */\nexport function extractAP2Mandate(sdJwtCompact: string): AP2MandateClaims | null {\n if (!sdJwtCompact || typeof sdJwtCompact !== 'string') return null;\n\n let decoded;\n try {\n decoded = decodeSdJwtSync(sdJwtCompact, sha256Sync);\n } catch {\n return null;\n }\n\n const payload = (decoded.jwt?.payload ?? {}) as Record<string, unknown>;\n const disclosures = decoded.disclosures ?? [];\n const claims = applyDisclosures(\n payload,\n disclosures as unknown as Array<{ key?: string; value?: unknown }>\n );\n\n const type = coerceMandateType(claims.type ?? claims.mandate_type ?? claims.mandateType);\n if (!type) return null;\n\n if (type === 'intent_mandate') return buildIntent(claims);\n if (type === 'cart_mandate') return buildCart(claims);\n return buildPayment(claims);\n}\n\nexport interface AP2MandateTripleInput {\n intent?: string;\n cart?: string;\n payment?: string;\n}\n\n/**\n * Extract an intent / cart / payment triple, returning whichever are present.\n * Does NOT enforce cross-reference consistency — that's ap2-verify.ts's job.\n */\nexport function extractAP2Mandates(input: AP2MandateTripleInput): AP2MandateTriple {\n const intent = input.intent\n ? (extractAP2Mandate(input.intent) as AP2IntentMandateClaims | null)\n : null;\n const cart = input.cart ? (extractAP2Mandate(input.cart) as AP2CartMandateClaims | null) : null;\n const payment = input.payment\n ? (extractAP2Mandate(input.payment) as AP2PaymentMandateClaims | null)\n : null;\n return {\n intent: intent ?? undefined,\n cart: cart ?? undefined,\n payment: payment ?? undefined,\n rawLayers: {\n intentJwt: input.intent,\n cartJwt: input.cart,\n paymentJwt: input.payment,\n },\n };\n}\n\nfunction buildIntent(claims: Record<string, unknown>): AP2IntentMandateClaims {\n return {\n type: 'intent_mandate',\n agent_id: coerceString(claims.agent_id ?? claims.agentId),\n user_id: coerceString(claims.user_id ?? claims.userId ?? claims.sub),\n merchant_category: coerceString(claims.merchant_category ?? claims.merchantCategory),\n allowedMerchantDomains: toStringArray(\n claims.allowed_merchant_domains ?? claims.allowedMerchantDomains\n ),\n paymentMethods: toStringArray(claims.payment_methods ?? claims.paymentMethods),\n expires: coerceString(claims.expires ?? claims.exp),\n payment_details_total: toPaymentDetails(claims.payment_details_total ?? claims.total),\n raw: claims,\n };\n}\n\nfunction buildCart(claims: Record<string, unknown>): AP2CartMandateClaims {\n return {\n type: 'cart_mandate',\n agent_id: coerceString(claims.agent_id ?? claims.agentId),\n intent_mandate_id: coerceString(claims.intent_mandate_id ?? claims.intentMandateId),\n merchant_id: coerceString(claims.merchant_id ?? claims.merchantId),\n line_items: toLineItems(claims.line_items ?? claims.lineItems),\n payment_details_total: toPaymentDetails(claims.payment_details_total ?? claims.total),\n expires: coerceString(claims.expires ?? claims.exp),\n raw: claims,\n };\n}\n\nfunction buildPayment(claims: Record<string, unknown>): AP2PaymentMandateClaims {\n return {\n type: 'payment_mandate',\n agent_id: coerceString(claims.agent_id ?? claims.agentId),\n cart_mandate_id: coerceString(claims.cart_mandate_id ?? claims.cartMandateId),\n payment_method: coerceString(claims.payment_method ?? claims.paymentMethod),\n payment_details_total: toPaymentDetails(claims.payment_details_total ?? claims.total),\n credential_provider: coerceString(claims.credential_provider ?? claims.credentialProvider),\n raw: claims,\n };\n}\n\nfunction toPaymentDetails(v: unknown): AP2PaymentDetailsTotal | undefined {\n if (!v || typeof v !== 'object') return undefined;\n const r = v as Record<string, unknown>;\n const amount = r.amount as Record<string, unknown> | undefined;\n return {\n amount: amount\n ? {\n value: coerceStringOrNumber(amount.value),\n currency: coerceString(amount.currency),\n }\n : undefined,\n label: coerceString(r.label),\n };\n}\n\nfunction toLineItems(v: unknown): AP2CartMandateClaims['line_items'] {\n if (!Array.isArray(v)) return undefined;\n const items: NonNullable<AP2CartMandateClaims['line_items']> = [];\n for (const item of v) {\n if (!item || typeof item !== 'object') continue;\n const r = item as Record<string, unknown>;\n const price = r.price as Record<string, unknown> | undefined;\n items.push({\n id: coerceString(r.id),\n quantity: coerceNumber(r.quantity),\n price: price\n ? {\n value: coerceStringOrNumber(price.value),\n currency: coerceString(price.currency),\n }\n : undefined,\n });\n }\n return items.length > 0 ? items : undefined;\n}\n\nfunction toStringArray(v: unknown): string[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out = v.filter((i): i is string => typeof i === 'string' && i.length > 0);\n return out.length > 0 ? out : undefined;\n}\n\nfunction applyDisclosures(\n payload: Record<string, unknown>,\n disclosures: Array<{ key?: string; value?: unknown }>\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...payload };\n for (const d of disclosures) {\n if (d.key && d.value !== undefined && !(d.key in result)) {\n result[d.key] = d.value;\n }\n }\n return result;\n}\n\nfunction coerceMandateType(v: unknown): AP2MandateType | null {\n if (v === 'intent_mandate' || v === 'cart_mandate' || v === 'payment_mandate') return v;\n return null;\n}\n\nfunction coerceString(v: unknown): string | undefined {\n return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n\nfunction coerceNumber(v: unknown): number | undefined {\n if (typeof v === 'number' && Number.isFinite(v)) return v;\n if (typeof v === 'string') {\n const n = Number(v);\n return Number.isFinite(n) ? n : undefined;\n }\n return undefined;\n}\n\nfunction coerceStringOrNumber(v: unknown): string | number | undefined {\n if (typeof v === 'number' && Number.isFinite(v)) return v;\n if (typeof v === 'string' && v.length > 0) return v;\n return undefined;\n}\n\nfunction sha256Sync(data: string | ArrayBuffer): Uint8Array {\n const buf =\n typeof data === 'string' ? Buffer.from(data, 'utf-8') : Buffer.from(new Uint8Array(data));\n const hash = createHash('sha256').update(buf).digest();\n return new Uint8Array(hash.buffer, hash.byteOffset, hash.byteLength);\n}\n","/**\n * MPP (Machine Payments Protocol) extractor.\n *\n * Wraps mppx (wevm) — pinned to 0.5.13, wrapped behind this adapter so\n * upgrades localise here. MPP launched March 18 2026 (Stripe + Tempo +\n * Paradigm), IETF draft-ryan-httpauth-payment-01.\n *\n * Flow:\n * Client → GET /resource\n * Server → 402 + WWW-Authenticate: Payment id=..., realm=..., method=tempo|stripe|...\n * Client → GET /resource with Authorization: Payment <base64url-json credential>\n * Server → 200 + Payment-Receipt: <base64url-json receipt>\n *\n * What we extract:\n * - Challenge: id, realm, method, intent, request{amount,currency,...}, expires, digest\n * - Credential: challenge + source (DID/chain-key) + payload (method-specific)\n * - Receipt: challengeId, method, reference (tx hash / pi_... ID), settlement\n * - Multi-method 402 offers (may be multiple WWW-Authenticate headers)\n *\n * What we do NOT verify here (pass-through):\n * - HMAC challenge binding (requires merchant's MPP_SECRET_KEY)\n * - Payment proof cryptography (Tempo tx sig, Stripe SPT, Lightning preimage)\n * — each requires upstream connectivity\n *\n * Verification (expiry + BodyDigest + source extraction) in mpp-verify.ts.\n */\n\nimport { Challenge, Credential, Receipt } from 'mppx';\n\nexport interface MPPChallengeSummary {\n id: string;\n realm: string;\n method: string;\n intent: string;\n /** Method-specific request data (amount, currency, recipient, etc.) */\n request: Record<string, unknown>;\n expires?: string;\n digest?: string;\n description?: string;\n opaque?: Record<string, string>;\n}\n\nexport interface MPPCredentialSummary {\n challenge: MPPChallengeSummary;\n /** DID or chain-native key identifying the payer. */\n source?: string;\n /** Method-specific payment proof (Tempo tx, SPT, Lightning preimage, etc.). */\n payload: unknown;\n}\n\nexport interface MPPReceiptSummary {\n method?: string;\n reference?: string;\n externalId?: string;\n status?: string;\n timestamp?: string;\n raw: Record<string, unknown>;\n}\n\nexport type MPPKind = 'challenge' | 'credential' | 'receipt' | 'error' | 'unknown';\n\nexport interface MPPRequestContext {\n kind: MPPKind;\n /** For 402 responses: one or more challenge offers. */\n challenges?: MPPChallengeSummary[];\n /** For requests with Authorization: Payment header. */\n credential?: MPPCredentialSummary;\n /** For 200 responses with Payment-Receipt header. */\n receipt?: MPPReceiptSummary;\n /** For problem+json error responses. */\n error?: { type?: string; title?: string; detail?: string };\n /** Detected payment methods offered (for multi-method 402). */\n offeredMethods?: string[];\n /** Raw body captured for BodyDigest verification in mpp-verify.ts. */\n rawBody?: string;\n}\n\nexport interface MPPRequestLike {\n method: string;\n url: string;\n headers: Record<string, string | string[] | undefined>;\n body?: unknown;\n rawBody?: string;\n}\n\nexport interface MPPResponseLike {\n status: number;\n headers: Record<string, string | string[] | undefined>;\n body?: unknown;\n rawBody?: string;\n}\n\n/**\n * Extract MPP context from an agent → merchant request.\n * Looks for `Authorization: Payment <credential>` header.\n */\nexport function extractMPPFromRequest(request: MPPRequestLike): MPPRequestContext | null {\n const auth = readHeader(request.headers, 'authorization');\n if (!auth || !/^\\s*Payment\\s+/i.test(auth)) return null;\n\n try {\n const credential = Credential.deserialize(auth);\n return {\n kind: 'credential',\n credential: {\n challenge: summarizeChallenge(credential.challenge),\n source: credential.source,\n payload: credential.payload,\n },\n rawBody: request.rawBody,\n };\n } catch {\n return { kind: 'error', error: { type: 'invalid-credential-encoding' } };\n }\n}\n\n/**\n * Extract MPP context from a merchant → agent response.\n * Handles 402 (challenge offers), 200 (receipt), 4xx (problem+json errors).\n */\nexport function extractMPPFromResponse(response: MPPResponseLike): MPPRequestContext | null {\n if (response.status === 402) {\n const challenges = collectChallenges(response);\n if (challenges.length === 0) return null;\n const methods = Array.from(new Set(challenges.map((c) => c.method)));\n return {\n kind: 'challenge',\n challenges,\n offeredMethods: methods,\n };\n }\n\n const receiptHeader = readHeader(response.headers, 'payment-receipt');\n if (receiptHeader) {\n try {\n const parsed = Receipt.deserialize(receiptHeader);\n const r = parsed as unknown as Record<string, unknown>;\n return {\n kind: 'receipt',\n receipt: {\n method: coerceString(r.method),\n reference: coerceString(r.reference),\n externalId: coerceString(r.externalId ?? r.external_id),\n status: coerceString(r.status),\n timestamp: coerceString(r.timestamp),\n raw: r,\n },\n };\n } catch {\n return { kind: 'error', error: { type: 'invalid-receipt-encoding' } };\n }\n }\n\n const contentType = readHeader(response.headers, 'content-type');\n if (contentType && /application\\/problem\\+json/i.test(contentType)) {\n const body =\n typeof response.body === 'object' && response.body !== null\n ? (response.body as Record<string, unknown>)\n : {};\n return {\n kind: 'error',\n error: {\n type: coerceString(body.type),\n title: coerceString(body.title),\n detail: coerceString(body.detail),\n },\n };\n }\n\n return null;\n}\n\n/**\n * Extract from either a request OR a response, auto-detecting which has MPP\n * artifacts. Convenience for pipeline callers.\n */\nexport function extractMPPContext(\n message:\n | { request: MPPRequestLike }\n | { response: MPPResponseLike }\n | (MPPRequestLike & Partial<MPPResponseLike>)\n): MPPRequestContext | null {\n if ('request' in message) return extractMPPFromRequest(message.request);\n if ('response' in message) return extractMPPFromResponse(message.response);\n if (typeof (message as MPPResponseLike).status === 'number') {\n return extractMPPFromResponse(message as MPPResponseLike);\n }\n return extractMPPFromRequest(message as MPPRequestLike);\n}\n\nfunction collectChallenges(response: MPPResponseLike): MPPChallengeSummary[] {\n const wwwAuth = readHeader(response.headers, 'www-authenticate');\n if (!wwwAuth) return [];\n const headers = new Headers();\n headers.set('www-authenticate', wwwAuth);\n\n const out: MPPChallengeSummary[] = [];\n try {\n const list = Challenge.fromHeadersList(headers);\n for (const ch of list) {\n out.push(summarizeChallenge(ch as unknown as Challenge.Challenge));\n }\n } catch {\n // fall through with empty list\n }\n return out;\n}\n\nfunction summarizeChallenge(\n ch: Challenge.Challenge | Record<string, unknown>\n): MPPChallengeSummary {\n const raw = ch as Record<string, unknown>;\n return {\n id: coerceString(raw.id) ?? '',\n realm: coerceString(raw.realm) ?? '',\n method: coerceString(raw.method) ?? '',\n intent: coerceString(raw.intent) ?? '',\n request: (raw.request as Record<string, unknown>) ?? {},\n expires: coerceString(raw.expires),\n digest: coerceString(raw.digest),\n description: coerceString(raw.description),\n opaque: raw.opaque as Record<string, string> | undefined,\n };\n}\n\nfunction readHeader(\n headers: Record<string, string | string[] | undefined>,\n name: string\n): string | undefined {\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === name) {\n const raw = headers[key];\n if (typeof raw === 'string') return raw;\n if (Array.isArray(raw)) return raw.join(', ');\n }\n }\n return undefined;\n}\n\nfunction coerceString(v: unknown): string | undefined {\n return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n","/**\n * MPP verification — expiry + optional BodyDigest + source extraction.\n *\n * We do NOT verify the challenge's HMAC binding (needs merchant's secret)\n * or the cryptographic payment proof (per-method, requires upstream\n * connectivity). Those are the merchant's / settlement layer's job.\n *\n * Our job: structural correctness, expiry policy, tamper detection via\n * optional BodyDigest, and identity extraction for PDLSS binding.\n */\n\nimport { BodyDigest } from 'mppx';\nimport type { MPPRequestContext } from './mpp';\n\nexport interface MPPVerifyInput {\n context: MPPRequestContext;\n /** Raw request body to validate BodyDigest against, if the challenge declares one. */\n rawBody?: string;\n /** Seconds of clock-skew tolerance on challenge.expires. Default 300. */\n clockSkewSec?: number;\n /** Injectable for deterministic tests. */\n now?: () => number;\n}\n\nexport interface MPPVerifyResult {\n ok: boolean;\n expiryOk: boolean;\n bodyDigestOk: boolean | null;\n source?: string;\n method?: string;\n error?: string;\n}\n\nexport function verifyMPP(input: MPPVerifyInput): MPPVerifyResult {\n const { context } = input;\n const tolerance = input.clockSkewSec ?? 300;\n const nowSec = input.now ? input.now() : Math.floor(Date.now() / 1000);\n\n // Extract the challenge under test — for credential flow, from inside the\n // wrapped challenge; for bare challenge flow, from context.challenges[0].\n const challenge = context.credential?.challenge ?? (context.challenges && context.challenges[0]);\n const source = context.credential?.source;\n const method = challenge?.method;\n\n let expiryOk = true;\n if (challenge?.expires) {\n const parsedExpiry = Date.parse(challenge.expires);\n if (!Number.isFinite(parsedExpiry)) {\n return {\n ok: false,\n expiryOk: false,\n bodyDigestOk: null,\n source,\n method,\n error: 'unparseable challenge.expires',\n };\n }\n const expiresSec = Math.floor(parsedExpiry / 1000);\n if (nowSec > expiresSec + tolerance) {\n expiryOk = false;\n }\n }\n\n let bodyDigestOk: boolean | null = null;\n if (challenge?.digest && input.rawBody !== undefined) {\n try {\n if (!/^sha-256=/.test(challenge.digest)) {\n bodyDigestOk = false;\n } else {\n bodyDigestOk = BodyDigest.verify(challenge.digest as `sha-256=${string}`, input.rawBody);\n }\n } catch {\n bodyDigestOk = false;\n }\n }\n\n const ok = expiryOk && (bodyDigestOk === null || bodyDigestOk === true);\n const errors: string[] = [];\n if (!expiryOk) errors.push('challenge expired');\n if (bodyDigestOk === false) errors.push('body digest mismatch');\n\n return {\n ok,\n expiryOk,\n bodyDigestOk,\n source,\n method,\n error: errors.length > 0 ? errors.join('; ') : undefined,\n };\n}\n","/**\n * x402 (Coinbase / Linux Foundation x402 Foundation) extractor.\n *\n * Wraps @x402/core's schema parsers. x402 Foundation launched April 2 2026\n * with v2 adding network-agnostic identifiers + multiple facilitators +\n * Bazaar discovery. MPP (Machine Payments Protocol) is the IETF-formalised\n * superset of x402; this module normalizes x402 output to MPP-shape so\n * downstream pipeline code is uniform.\n *\n * Where x402 lives on the wire:\n * - 402 response body (v2) OR `X-PAYMENT-REQUIRED` header (v1) — PaymentRequired\n * - Request body (v2) OR `X-PAYMENT` header (v1, base64) — PaymentPayload\n */\n\nimport {\n validatePaymentRequired,\n validatePaymentPayload,\n type PaymentRequired,\n type PaymentPayload,\n type PaymentRequirements,\n} from '@x402/core/schemas';\nimport { safeBase64Decode } from '@x402/core/utils';\n\nexport type X402Kind = 'required' | 'payload' | 'error' | 'unknown';\n\nexport interface X402RequirementsSummary {\n scheme: string;\n network: string;\n asset: string;\n /** Normalized to string for v1/v2 compat — v1 uses maxAmountRequired, v2 uses amount. */\n amount: string;\n payTo: string;\n maxTimeoutSeconds?: number;\n resource?: string;\n description?: string;\n}\n\nexport interface X402RequestContext {\n kind: X402Kind;\n version: 1 | 2 | null;\n /** For 402 responses: the PaymentRequired body. */\n paymentRequired?: {\n resource: string;\n accepts: X402RequirementsSummary[];\n extensions?: Record<string, unknown>;\n error?: string;\n };\n /** For request body (v2) or X-PAYMENT header (v1 base64): the PaymentPayload. */\n paymentPayload?: {\n scheme: string;\n network: string;\n /** Free-form per-scheme payload (e.g. EIP-3009 authorization, Solana tx). */\n payload: Record<string, unknown>;\n extensions?: Record<string, unknown>;\n };\n error?: { type: string; detail?: string };\n /** Whether this was parsed from a header (v1 back-compat) or body (v2). */\n source: 'header' | 'body' | null;\n}\n\nexport interface X402RequestLike {\n method?: string;\n url?: string;\n headers?: Record<string, string | string[] | undefined>;\n body?: unknown;\n}\n\nexport interface X402ResponseLike {\n status?: number;\n headers?: Record<string, string | string[] | undefined>;\n body?: unknown;\n}\n\n/**\n * Extract x402 PaymentPayload from an agent → merchant request.\n * Checks v2 body (if it parses as PaymentPayload) and v1 X-PAYMENT header.\n */\nexport function extractX402FromRequest(request: X402RequestLike): X402RequestContext | null {\n const headerValue = readHeader(request.headers, 'x-payment');\n\n // v2 body path first\n if (request.body && typeof request.body === 'object') {\n const parsed = tryParsePayload(request.body);\n if (parsed) return buildPayloadContext(parsed, 'body');\n }\n\n // v1 header path\n if (headerValue) {\n try {\n const decoded = safeBase64Decode(headerValue);\n if (decoded) {\n const json = JSON.parse(decoded);\n const parsed = tryParsePayload(json);\n if (parsed) return buildPayloadContext(parsed, 'header');\n }\n } catch {\n return {\n kind: 'error',\n version: 1,\n source: 'header',\n error: { type: 'invalid-x402-payload' },\n };\n }\n }\n\n return null;\n}\n\n/**\n * Extract x402 PaymentRequired from a merchant → agent 402 response.\n */\nexport function extractX402FromResponse(response: X402ResponseLike): X402RequestContext | null {\n if (response.status !== 402) return null;\n\n // v2 body path\n if (response.body && typeof response.body === 'object') {\n const parsed = tryParseRequired(response.body);\n if (parsed) return buildRequiredContext(parsed, 'body');\n }\n\n // v1 header path\n const headerValue = readHeader(response.headers, 'x-payment-required');\n if (headerValue) {\n try {\n const decoded = safeBase64Decode(headerValue);\n if (decoded) {\n const json = JSON.parse(decoded);\n const parsed = tryParseRequired(json);\n if (parsed) return buildRequiredContext(parsed, 'header');\n }\n } catch {\n return {\n kind: 'error',\n version: 1,\n source: 'header',\n error: { type: 'invalid-x402-required' },\n };\n }\n }\n\n return null;\n}\n\nexport function extractX402Context(\n message:\n | { request: X402RequestLike }\n | { response: X402ResponseLike }\n | (X402RequestLike & Partial<X402ResponseLike>)\n): X402RequestContext | null {\n if ('request' in message) return extractX402FromRequest(message.request);\n if ('response' in message) return extractX402FromResponse(message.response);\n if (typeof (message as X402ResponseLike).status === 'number') {\n return extractX402FromResponse(message as X402ResponseLike);\n }\n return extractX402FromRequest(message as X402RequestLike);\n}\n\nfunction tryParseRequired(data: unknown): PaymentRequired | null {\n try {\n return validatePaymentRequired(data);\n } catch {\n return null;\n }\n}\n\nfunction tryParsePayload(data: unknown): PaymentPayload | null {\n try {\n return validatePaymentPayload(data);\n } catch {\n return null;\n }\n}\n\nfunction buildRequiredContext(\n parsed: PaymentRequired,\n source: 'header' | 'body'\n): X402RequestContext {\n const asRecord = parsed as unknown as Record<string, unknown>;\n const version = coerceVersion(asRecord.x402Version);\n const accepts = (asRecord.accepts as PaymentRequirements[] | undefined) ?? [];\n return {\n kind: 'required',\n version,\n source,\n paymentRequired: {\n resource: resolveResource(asRecord.resource),\n accepts: accepts.map(summarizeRequirement),\n extensions: asRecord.extensions as Record<string, unknown> | undefined,\n error: typeof asRecord.error === 'string' ? asRecord.error : undefined,\n },\n };\n}\n\nfunction buildPayloadContext(\n parsed: PaymentPayload,\n source: 'header' | 'body'\n): X402RequestContext {\n const asRecord = parsed as unknown as Record<string, unknown>;\n const version = coerceVersion(asRecord.x402Version);\n const accepted = asRecord.accepted as PaymentRequirements | undefined;\n const payload = (asRecord.payload as Record<string, unknown>) ?? {};\n return {\n kind: 'payload',\n version,\n source,\n paymentPayload: {\n scheme: accepted?.scheme ?? (typeof asRecord.scheme === 'string' ? asRecord.scheme : ''),\n network: accepted?.network ?? (typeof asRecord.network === 'string' ? asRecord.network : ''),\n payload,\n extensions: asRecord.extensions as Record<string, unknown> | undefined,\n },\n };\n}\n\nfunction summarizeRequirement(req: PaymentRequirements): X402RequirementsSummary {\n const r = req as unknown as Record<string, unknown>;\n const amount = (r.amount ?? r.maxAmountRequired ?? '0') as string;\n return {\n scheme: (r.scheme as string) ?? '',\n network: (r.network as string) ?? '',\n asset: (r.asset as string) ?? '',\n amount: String(amount),\n payTo: (r.payTo as string) ?? '',\n maxTimeoutSeconds: typeof r.maxTimeoutSeconds === 'number' ? r.maxTimeoutSeconds : undefined,\n resource: typeof r.resource === 'string' ? r.resource : undefined,\n description: typeof r.description === 'string' ? r.description : undefined,\n };\n}\n\nfunction resolveResource(v: unknown): string {\n if (typeof v === 'string') return v;\n if (v && typeof v === 'object' && 'url' in v && typeof (v as { url: unknown }).url === 'string') {\n return (v as { url: string }).url;\n }\n return '';\n}\n\nfunction coerceVersion(v: unknown): 1 | 2 | null {\n if (v === 1 || v === 2) return v;\n return null;\n}\n\nfunction readHeader(\n headers: Record<string, string | string[] | undefined> | undefined,\n name: string\n): string | undefined {\n if (!headers) return undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === name) {\n const raw = headers[key];\n if (typeof raw === 'string') return raw;\n if (Array.isArray(raw)) return raw[0];\n }\n }\n return undefined;\n}\n","/**\n * VI (Verifiable Intent) 3-layer SD-JWT chain verification.\n *\n * VI chains: L1 (credential provider → wallet) → L2 (user → agent) → L3\n * (agent → merchant). L3 itself can split into L3a (payment mandate) + L3b\n * (checkout mandate) cross-referenced via transaction_id, with L3b carrying\n * a checkout_hash (VI constraint type 8) that must match SHA-256 of the L2\n * checkout disclosure.\n *\n * Signature primitives are delegated to @sd-jwt/core (via our extractor);\n * cnf.jwk chain-walking + cross-references + checkout_hash binding is\n * AstraSync-specific composition logic — that's the whitespace here.\n *\n * This module does NOT re-verify selective-disclosure hashes (the extractor\n * already applied them via @sd-jwt/decode). It DOES verify:\n * - cnf.jwk in L1 payload points to L2's signing key (thumbprint match)\n * - cnf.jwk in L2 payload points to L3's signing key\n * - L3a.transaction_id === L3b.transaction_id (when both present)\n * - L3b.checkout_hash === SHA-256(L2 canonical checkout disclosure) — type 8\n * - mandate-level `exp` is not in the past (beyond clock skew)\n *\n * Cryptographic signature verification on each layer uses the verifier\n * callback the caller supplies (e.g. resolves via @sd-jwt/core with the\n * right JWK from the L1 issuer's JWKS).\n */\n\nimport { createHash, webcrypto } from 'node:crypto';\nimport type { JWK } from 'jose';\n\nexport interface VILayer {\n /** Compact SD-JWT / JWS for this layer. */\n compact: string;\n /** Decoded JWT payload (already disclosure-merged). */\n payload: Record<string, unknown>;\n /** Decoded JWT header. */\n header: Record<string, unknown>;\n}\n\nexport interface VIVerifyInput {\n /**\n * Layers in chain order. L1 may be omitted when the caller has already\n * resolved the chain via a trusted wallet binding.\n */\n layers: {\n l1?: VILayer;\n l2: VILayer;\n l3a?: VILayer;\n l3b?: VILayer;\n };\n /**\n * Verifier callback invoked per layer. Should return true iff the layer's\n * JWS signature verifies against the resolved public key (for L2 this is\n * L1's cnf.jwk; for L3 this is L2's cnf.jwk; for L1 this is the issuer's\n * JWKS per `iss` claim).\n */\n verifySignature: (layer: VILayer, expectedKey: JWK | null) => Promise<boolean>;\n clockSkewSec?: number;\n now?: () => number;\n}\n\nexport interface VIVerifyResult {\n ok: boolean;\n checks: {\n l1SigOk: boolean | null;\n l2SigOk: boolean;\n l3aSigOk: boolean | null;\n l3bSigOk: boolean | null;\n l1BindsL2: boolean;\n l2BindsL3: boolean;\n l3aL3bTxnIdMatch: boolean | null;\n checkoutHashOk: boolean | null;\n expiryOk: boolean;\n };\n errors: string[];\n}\n\nexport async function verifyVIChain(input: VIVerifyInput): Promise<VIVerifyResult> {\n const errors: string[] = [];\n const tolerance = input.clockSkewSec ?? 300;\n const now = input.now ? input.now() : Math.floor(Date.now() / 1000);\n const { l1, l2, l3a, l3b } = input.layers;\n\n // Signature verification ---------------------------------\n const l1SigOk = l1 ? await input.verifySignature(l1, null) : null;\n if (l1 && !l1SigOk) errors.push('L1 signature invalid');\n\n const l1Cnf = extractCnfJwk(l1?.payload);\n const l2SigOk = await input.verifySignature(l2, l1Cnf ?? null);\n if (!l2SigOk) errors.push('L2 signature invalid');\n\n const l2Cnf = extractCnfJwk(l2.payload);\n const l3aSigOk = l3a ? await input.verifySignature(l3a, l2Cnf ?? null) : null;\n if (l3a && !l3aSigOk) errors.push('L3a signature invalid');\n const l3bSigOk = l3b ? await input.verifySignature(l3b, l2Cnf ?? null) : null;\n if (l3b && !l3bSigOk) errors.push('L3b signature invalid');\n\n // cnf.jwk binding ----------------------------------------\n let l1BindsL2 = true;\n if (l1Cnf) {\n const l2KeyFromHeader = await jwkForLayer(l2);\n l1BindsL2 = l2KeyFromHeader ? await thumbprintsMatch(l1Cnf, l2KeyFromHeader) : false;\n if (!l1BindsL2) errors.push('L1.cnf.jwk does not bind L2 signing key');\n }\n\n let l2BindsL3 = true;\n if (l2Cnf && (l3a || l3b)) {\n const l3Layer = l3a ?? l3b!;\n const l3KeyFromHeader = await jwkForLayer(l3Layer);\n l2BindsL3 = l3KeyFromHeader ? await thumbprintsMatch(l2Cnf, l3KeyFromHeader) : false;\n if (!l2BindsL3) errors.push('L2.cnf.jwk does not bind L3 signing key');\n }\n\n // L3a/L3b cross-reference --------------------------------\n let l3aL3bTxnIdMatch: boolean | null = null;\n if (l3a && l3b) {\n const a = coerceString(l3a.payload.transaction_id ?? l3a.payload.transactionId);\n const b = coerceString(l3b.payload.transaction_id ?? l3b.payload.transactionId);\n if (a && b) {\n l3aL3bTxnIdMatch = a === b;\n if (!l3aL3bTxnIdMatch) {\n errors.push(`L3a.transaction_id (${a}) does not match L3b.transaction_id (${b})`);\n }\n }\n }\n\n // checkout_hash (VI constraint type 8) -------------------\n let checkoutHashOk: boolean | null = null;\n if (l3b) {\n const declaredHash = coerceString(\n l3b.payload.checkout_hash ??\n l3b.payload.conditional_transaction_id ??\n (l3b.payload.payment_reference as Record<string, unknown> | undefined)?.checkout_hash\n );\n if (declaredHash) {\n const computed = computeCheckoutHashFromL2(l2);\n checkoutHashOk = computed ? declaredHash === computed : false;\n if (!checkoutHashOk) {\n errors.push('L3b.checkout_hash does not match SHA-256 of L2 checkout disclosure');\n }\n }\n }\n\n // Expiry policy ------------------------------------------\n const expiryOk = checkExpiryAcross([l1, l2, l3a, l3b], tolerance, now, errors);\n\n const ok =\n l1SigOk !== false &&\n l2SigOk &&\n l3aSigOk !== false &&\n l3bSigOk !== false &&\n l1BindsL2 &&\n l2BindsL3 &&\n l3aL3bTxnIdMatch !== false &&\n checkoutHashOk !== false &&\n expiryOk;\n\n return {\n ok,\n checks: {\n l1SigOk,\n l2SigOk,\n l3aSigOk,\n l3bSigOk,\n l1BindsL2,\n l2BindsL3,\n l3aL3bTxnIdMatch,\n checkoutHashOk,\n expiryOk,\n },\n errors,\n };\n}\n\nfunction extractCnfJwk(payload: Record<string, unknown> | undefined): JWK | null {\n if (!payload) return null;\n const cnf = payload.cnf as Record<string, unknown> | undefined;\n if (!cnf) return null;\n const jwk = cnf.jwk as JWK | undefined;\n return jwk ?? null;\n}\n\nasync function jwkForLayer(layer: VILayer): Promise<JWK | null> {\n // Prefer explicit cnf.jwk in the header, then payload; fallback to null.\n const fromHeader = extractCnfJwk(layer.header);\n if (fromHeader) return fromHeader;\n const fromPayload = extractCnfJwk(layer.payload);\n return fromPayload;\n}\n\nasync function thumbprintsMatch(a: JWK, b: JWK): Promise<boolean> {\n try {\n const ta = await jwkThumbprint(a);\n const tb = await jwkThumbprint(b);\n return ta === tb;\n } catch {\n return false;\n }\n}\n\n// RFC 7638 thumbprint: SHA-256 over the canonical JSON of required JWK members.\nasync function jwkThumbprint(jwk: JWK): Promise<string> {\n const canonical = canonicalJwk(jwk);\n const bytes = new TextEncoder().encode(JSON.stringify(canonical));\n const subtle = webcrypto.subtle as SubtleCrypto;\n const buffer = await new Promise<ArrayBuffer>((resolve, reject) => {\n const source = new ArrayBuffer(bytes.byteLength);\n new Uint8Array(source).set(bytes);\n subtle.digest('SHA-256', source).then(resolve).catch(reject);\n });\n return Buffer.from(new Uint8Array(buffer)).toString('base64url').replace(/=+$/, '');\n}\n\nfunction canonicalJwk(jwk: JWK): Record<string, string> {\n // Per RFC 7638: members must appear in lexicographic order; only required\n // fields per kty are included.\n if (jwk.kty === 'EC') {\n return { crv: jwk.crv ?? '', kty: 'EC', x: jwk.x ?? '', y: jwk.y ?? '' };\n }\n if (jwk.kty === 'OKP') {\n return { crv: jwk.crv ?? '', kty: 'OKP', x: jwk.x ?? '' };\n }\n if (jwk.kty === 'RSA') {\n return { e: jwk.e ?? '', kty: 'RSA', n: jwk.n ?? '' };\n }\n return { kty: jwk.kty ?? '' };\n}\n\nfunction computeCheckoutHashFromL2(l2: VILayer): string | null {\n const checkoutDisclosure = (l2.payload.checkout ?? l2.payload.checkout_mandate) as unknown;\n if (!checkoutDisclosure) return null;\n const canonical = canonicalStringify(checkoutDisclosure);\n const hash = createHash('sha256').update(canonical).digest('base64url').replace(/=+$/, '');\n return hash;\n}\n\nfunction canonicalStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) return '[' + value.map(canonicalStringify).join(',') + ']';\n const entries = Object.entries(value as Record<string, unknown>).sort(([a], [b]) =>\n a < b ? -1 : a > b ? 1 : 0\n );\n return (\n '{' + entries.map(([k, v]) => JSON.stringify(k) + ':' + canonicalStringify(v)).join(',') + '}'\n );\n}\n\nfunction checkExpiryAcross(\n layers: Array<VILayer | undefined>,\n toleranceSec: number,\n nowSec: number,\n errors: string[]\n): boolean {\n let ok = true;\n const names = ['L1', 'L2', 'L3a', 'L3b'];\n layers.forEach((layer, idx) => {\n if (!layer) return;\n const exp = toUnixSeconds(layer.payload.exp ?? layer.payload.expires);\n if (exp === undefined) return;\n if (nowSec > exp + toleranceSec) {\n errors.push(`${names[idx]} mandate expired at ${exp}`);\n ok = false;\n }\n });\n return ok;\n}\n\nfunction toUnixSeconds(v: unknown): number | undefined {\n if (typeof v === 'number' && Number.isFinite(v)) return v;\n if (typeof v === 'string') {\n const asInt = Number(v);\n if (Number.isFinite(asInt) && asInt > 0) {\n return asInt >= 1e12 ? Math.floor(asInt / 1000) : Math.floor(asInt);\n }\n const parsed = Date.parse(v);\n if (Number.isFinite(parsed)) return Math.floor(parsed / 1000);\n }\n return undefined;\n}\n\nfunction coerceString(v: unknown): string | undefined {\n return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n","/**\n * Visa JWKS registry resolver.\n *\n * Default endpoint: https://mcp.visa.com/.well-known/jwks (per Visa TAP spec).\n * Wraps jose.createRemoteJWKSet which handles caching + rotation natively.\n */\n\nimport { createRemoteJWKSet, type JWK } from 'jose';\nimport type { RegistryResolver, ResolveContext } from './types';\n\nconst DEFAULT_VISA_JWKS_URL = 'https://mcp.visa.com/.well-known/jwks';\n\nexport interface VisaRegistryOptions {\n jwksUrl?: string;\n cacheMaxAge?: number;\n cooldownDuration?: number;\n}\n\nexport function createVisaRegistry(options: VisaRegistryOptions = {}): RegistryResolver {\n const url = new URL(options.jwksUrl ?? DEFAULT_VISA_JWKS_URL);\n const jwks = createRemoteJWKSet(url, {\n cacheMaxAge: options.cacheMaxAge,\n cooldownDuration: options.cooldownDuration,\n });\n\n return {\n name: 'visa',\n async resolve(kid: string, context?: ResolveContext): Promise<JWK | null> {\n if (!kid) return null;\n try {\n const key = await jwks({\n kid,\n alg: context?.algorithm ?? 'ES256',\n typ: 'JWT',\n });\n return exportJwkFromKeyLike(key);\n } catch {\n return null;\n }\n },\n };\n}\n\nasync function exportJwkFromKeyLike(keyLike: unknown): Promise<JWK | null> {\n if (!keyLike) return null;\n // jose returns KeyObject or CryptoKey — both export via exportJWK at caller side.\n // Runtime shape check: if it already looks like a JWK, pass through.\n if (typeof keyLike === 'object' && 'kty' in (keyLike as object)) {\n return keyLike as JWK;\n }\n const { exportJWK } = await import('jose');\n try {\n return await exportJWK(keyLike as Parameters<typeof exportJWK>[0]);\n } catch {\n return null;\n }\n}\n","/**\n * Cross-Protocol Transport Module\n *\n * Provides adapters for injecting/extracting AstraSync credentials\n * across HTTP, A2A, and MCP protocols.\n */\n\nimport type { AstraSyncCredentials, ProtocolTransport } from '../types';\nimport { setHttpHeaders, extractHttpCredentials } from './http';\nimport { setA2AMetadata, extractA2ACredentials } from './a2a';\nimport { setMcpMeta, extractMcpCredentials } from './mcp';\n\nexport { setHttpHeaders, extractHttpCredentials } from './http';\nexport { setA2AMetadata, extractA2ACredentials } from './a2a';\nexport { setMcpMeta, extractMcpCredentials } from './mcp';\n\n// Commerce protocol extractors + verifiers (PR 4+5)\nexport * from './purpose-mapping';\nexport * from './transaction-value';\nexport * from './rfc9421';\nexport * from './rfc9421-verify';\nexport * from './ucp';\nexport * from './acp';\nexport * from './vi';\nexport * from './stripe-webhook';\nexport * from './constraint-eval';\nexport * from './identity-binding';\nexport * from './ap2';\nexport * from './ap2-verify';\nexport * from './acp-verify';\nexport * from './mpp';\nexport * from './mpp-verify';\nexport * from './x402';\nexport * from './vi-verify';\nexport * from './commerce-pipeline';\nexport * from './extractor-registry';\nexport * from './registry/types';\nexport { createVisaRegistry } from './registry/visa';\nexport { createMastercardRegistry } from './registry/mastercard';\nexport { createWebBotAuthRegistry } from './registry/web-bot-auth';\n\n/**\n * Auto-detect protocol from request/context shape.\n */\nexport function detectProtocol(context: Record<string, unknown>): ProtocolTransport {\n // A2A: has metadata block with task-like structure\n if (context.metadata && typeof context.metadata === 'object') {\n return 'a2a';\n }\n\n // MCP: has _meta block (MCP convention)\n if (context._meta && typeof context._meta === 'object') {\n return 'mcp';\n }\n\n // Default to HTTP\n return 'http';\n}\n\n/**\n * Apply credentials to any protocol target.\n */\nexport function applyCredentials(\n protocol: ProtocolTransport,\n target: Record<string, unknown>,\n credentials: AstraSyncCredentials\n): Record<string, unknown> {\n switch (protocol) {\n case 'http':\n return setHttpHeaders(target as Record<string, string>, credentials);\n case 'a2a':\n return setA2AMetadata(target, credentials);\n case 'mcp':\n return setMcpMeta(target, credentials);\n default:\n return target;\n }\n}\n\n/**\n * Extract credentials from any protocol context.\n */\nexport function extractCredentialsFromProtocol(\n protocol: ProtocolTransport,\n context: Record<string, unknown>\n): AstraSyncCredentials | null {\n switch (protocol) {\n case 'http':\n return extractHttpCredentials(context as Record<string, string | string[] | undefined>);\n case 'a2a':\n return extractA2ACredentials(context);\n case 'mcp':\n return extractMcpCredentials(context);\n default:\n return null;\n }\n}\n","/**\n * Agent-side SDK errors.\n */\n\nexport class AstraSyncSdkError extends Error {\n readonly code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'AstraSyncSdkError';\n this.code = code;\n }\n}\n\n/**\n * Thrown when the API key used to initialise AgentClient is not owned by\n * the same account that registered the configured ASTRA-id. Blocks the\n * client from doing anything under a mismatched identity.\n */\nexport class OwnershipMismatchError extends AstraSyncSdkError {\n readonly astraId: string;\n\n constructor(astraId: string) {\n super(\n 'ownership_mismatch',\n `The configured API key does not own agent ${astraId}. Refusing to initialise.`\n );\n this.name = 'OwnershipMismatchError';\n this.astraId = astraId;\n }\n}\n","/**\n * AgentClient — Credential Presentation\n *\n * Agent-side SDK for automatically injecting AstraSync credentials\n * into outgoing requests across all supported protocols.\n */\n\nimport type { AstraSyncCredentials, ProtocolTransport } from '../types';\nimport { setHttpHeaders } from '../transport/http';\nimport { setA2AMetadata } from '../transport/a2a';\nimport { setMcpMeta } from '../transport/mcp';\nimport { applyCredentials } from '../transport';\nimport { OwnershipMismatchError } from './errors';\n\ninterface AgentClientConfig {\n agentId: string;\n verifyUrl?: string;\n challengeUrl?: string;\n pdlss?: AstraSyncCredentials['pdlss'];\n /** Base URL for AstraSync API (used for ownership check). Defaults to https://astrasync.ai/api */\n apiBaseUrl?: string;\n /** API key used to authenticate ownership check + other authenticated calls. */\n apiKey?: string;\n}\n\ninterface FetchOptions extends RequestInit {\n purpose?: string;\n action?: string;\n}\n\nexport class AgentClient {\n private credentials: AstraSyncCredentials;\n private apiBaseUrl: string;\n private apiKey: string | undefined;\n\n constructor(config: AgentClientConfig) {\n this.credentials = {\n agentId: config.agentId,\n verifyUrl: config.verifyUrl ?? 'https://astrasync.ai/api/agents/verify-access',\n challengeUrl: config.challengeUrl,\n pdlss: config.pdlss,\n };\n this.apiBaseUrl = config.apiBaseUrl ?? 'https://astrasync.ai/api';\n this.apiKey = config.apiKey;\n }\n\n /**\n * Async factory that validates the API key's account owns the configured\n * ASTRA-id before returning a usable client. Refuses to initialise on\n * mismatch so a stolen ASTRA-id cannot be paired with a valid (different)\n * API key.\n *\n * Set env `ASTRASYNC_SKIP_OWNERSHIP_CHECK=true` to bypass — intended for\n * test environments only.\n */\n static async create(config: AgentClientConfig): Promise<AgentClient> {\n const client = new AgentClient(config);\n\n const skip =\n typeof process !== 'undefined' && process.env?.ASTRASYNC_SKIP_OWNERSHIP_CHECK === 'true';\n if (skip) return client;\n\n if (!config.apiKey) {\n // Without an API key we cannot bind ownership. Surface this as the\n // same refusal so misconfigured clients fail loudly.\n throw new OwnershipMismatchError(config.agentId);\n }\n\n const owned = await client.verifyOwnership();\n if (!owned) throw new OwnershipMismatchError(config.agentId);\n return client;\n }\n\n /**\n * Calls GET /api/agents/:astraId/ownership with the configured API key.\n * Returns true only when the backend confirms this API key's account\n * owns the configured agent.\n */\n private async verifyOwnership(): Promise<boolean> {\n if (!this.apiKey) return false;\n const url = `${this.apiBaseUrl.replace(/\\/+$/, '')}/api/agents/${encodeURIComponent(\n this.credentials.agentId\n )}/ownership`;\n const resp = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n });\n if (!resp.ok) return false;\n const body = (await resp.json().catch(() => null)) as {\n success?: boolean;\n data?: { owned?: boolean };\n } | null;\n return Boolean(body?.data?.owned);\n }\n\n /**\n * Make an HTTP request with AstraSync headers automatically injected.\n */\n async fetch(url: string, options?: FetchOptions): Promise<Response> {\n const { purpose, action, ...fetchOptions } = options ?? {};\n\n // Build credentials with optional overrides\n const creds: AstraSyncCredentials = { ...this.credentials };\n if (purpose) {\n creds.pdlss = {\n ...creds.pdlss,\n purpose: { category: purpose, action },\n };\n }\n\n // Inject AstraSync headers\n const existingHeaders: Record<string, string> = {};\n if (fetchOptions.headers) {\n if (fetchOptions.headers instanceof Headers) {\n fetchOptions.headers.forEach((value, key) => {\n existingHeaders[key] = value;\n });\n } else if (Array.isArray(fetchOptions.headers)) {\n for (const [key, value] of fetchOptions.headers) {\n existingHeaders[key] = value;\n }\n } else {\n Object.assign(existingHeaders, fetchOptions.headers);\n }\n }\n\n const enrichedHeaders = setHttpHeaders(existingHeaders, creds);\n\n return fetch(url, {\n ...fetchOptions,\n headers: enrichedHeaders,\n });\n }\n\n /**\n * Prepare A2A task metadata with AstraSync credentials.\n */\n prepareA2AMetadata(\n task: Record<string, unknown>,\n overrides?: { purpose?: string; action?: string }\n ): Record<string, unknown> {\n const creds = this.buildCredentials(overrides);\n return setA2AMetadata(task, creds);\n }\n\n /**\n * Prepare MCP params with AstraSync _meta.\n */\n prepareMcpMeta(\n params: Record<string, unknown>,\n overrides?: { purpose?: string; action?: string }\n ): Record<string, unknown> {\n const creds = this.buildCredentials(overrides);\n return setMcpMeta(params, creds);\n }\n\n /**\n * Generic: apply credentials to any protocol.\n */\n applyCredentials(\n protocol: ProtocolTransport,\n target: Record<string, unknown>,\n overrides?: { purpose?: string; action?: string }\n ): Record<string, unknown> {\n const creds = this.buildCredentials(overrides);\n return applyCredentials(protocol, target, creds);\n }\n\n private buildCredentials(overrides?: {\n purpose?: string;\n action?: string;\n }): AstraSyncCredentials {\n if (!overrides?.purpose) return this.credentials;\n\n return {\n ...this.credentials,\n pdlss: {\n ...this.credentials.pdlss,\n purpose: { category: overrides.purpose, action: overrides.action },\n },\n };\n }\n}\n","/**\n * ChallengeHandler — Agent-Side Runtime Challenge Responder\n *\n * Handles incoming runtime challenges from AstraSync's verification service.\n * Agents register pending counterparties before initiating contact,\n * then this handler validates and responds to challenges.\n */\n\ninterface ChallengePayload {\n challengeId: string;\n type: string;\n counterpartyId?: string | null;\n counterpartyUrl?: string | null;\n question?: string;\n issuedAt: string;\n expiresAt: string;\n}\n\ninterface ChallengeResponse {\n status: number;\n body: {\n challengeId: string;\n acknowledged: boolean;\n pendingCounterparties: string[];\n respondedAt: string;\n error?: string;\n };\n}\n\ninterface ChallengeHandlerConfig {\n agentId: string;\n}\n\nexport class ChallengeHandler {\n private agentId: string;\n private pendingCounterparties: Set<string> = new Set();\n\n constructor(config: ChallengeHandlerConfig) {\n this.agentId = config.agentId;\n }\n\n /**\n * Register a counterparty as pending (before initiating contact).\n */\n registerPending(counterpartyId: string): void {\n this.pendingCounterparties.add(counterpartyId);\n }\n\n /**\n * Remove a counterparty from pending list (after interaction complete).\n */\n removePending(counterpartyId: string): void {\n this.pendingCounterparties.delete(counterpartyId);\n }\n\n /**\n * Get current pending counterparties list.\n */\n getPendingList(): string[] {\n return [...this.pendingCounterparties];\n }\n\n /**\n * Express middleware for the challenge endpoint.\n * Mount at: app.post('/astrasync/challenge', handler.expressMiddleware())\n */\n expressMiddleware(): (req: { body: unknown }, res: { status: (code: number) => { json: (body: unknown) => void } }) => void {\n return (req, res) => {\n const result = this.handleChallenge(req.body);\n res.status(result.status).json(result.body);\n };\n }\n\n /**\n * Generic handler (framework-agnostic).\n * Returns { status, body } for the caller to send.\n */\n handleChallenge(body: unknown): ChallengeResponse {\n // Validate payload shape\n if (!body || typeof body !== 'object') {\n return {\n status: 400,\n body: {\n challengeId: '',\n acknowledged: false,\n pendingCounterparties: [],\n respondedAt: new Date().toISOString(),\n error: 'Invalid challenge payload',\n },\n };\n }\n\n const payload = body as ChallengePayload;\n\n if (!payload.challengeId || !payload.issuedAt || !payload.expiresAt) {\n return {\n status: 400,\n body: {\n challengeId: payload.challengeId ?? '',\n acknowledged: false,\n pendingCounterparties: [],\n respondedAt: new Date().toISOString(),\n error: 'Missing required challenge fields',\n },\n };\n }\n\n // Check if challenge has expired\n const now = new Date();\n const expiresAt = new Date(payload.expiresAt);\n if (now > expiresAt) {\n return {\n status: 410,\n body: {\n challengeId: payload.challengeId,\n acknowledged: false,\n pendingCounterparties: [],\n respondedAt: now.toISOString(),\n error: 'Challenge has expired',\n },\n };\n }\n\n // Respond with current pending list\n return {\n status: 200,\n body: {\n challengeId: payload.challengeId,\n acknowledged: true,\n pendingCounterparties: this.getPendingList(),\n respondedAt: now.toISOString(),\n },\n };\n }\n}\n","/**\n * PDLSS Formatter — Transport Format Conversion\n *\n * Converts between full PDLSS boundaries and compact transport format\n * used in HTTP headers, A2A metadata, and MCP _meta blocks.\n */\n\nimport type { AstraSyncCredentials } from '../types';\n\n/**\n * Full PDLSS configuration (as returned by the backend).\n */\nexport interface PDLSSConfig {\n purpose?: {\n categories?: string[];\n allowedActions?: string[];\n deniedActions?: string[];\n };\n duration?: {\n maxSessionDuration?: number;\n ttl?: number;\n allowedDays?: number[];\n allowedHours?: { start: number; end: number };\n };\n limits?: {\n autonomousThreshold?: number;\n stepUpThreshold?: number;\n approvalThreshold?: number;\n currency?: string;\n };\n scope?: {\n jurisdictions?: string[];\n resources?: string[];\n resourceTypes?: string[];\n };\n selfInstantiation?: {\n allowed: boolean;\n maxDepth?: number;\n maxSubAgents?: number;\n };\n}\n\n/**\n * Compact transport format (embedded in headers/metadata).\n */\nexport type TransportPDLSS = NonNullable<AstraSyncCredentials['pdlss']>;\n\n/**\n * Convert full PDLSS boundaries into compact transport format.\n * Used by AgentClient when building credential headers/metadata.\n */\nexport function formatPDLSSForTransport(pdlss: PDLSSConfig): TransportPDLSS {\n const transport: TransportPDLSS = {};\n\n // Purpose: pick the primary category and first allowed action\n if (pdlss.purpose?.categories?.length) {\n transport.purpose = {\n category: pdlss.purpose.categories[0],\n action: pdlss.purpose.allowedActions?.[0],\n };\n }\n\n // Duration: use the shorter of maxSessionDuration and ttl\n if (pdlss.duration) {\n const candidates: number[] = [];\n if (pdlss.duration.maxSessionDuration) candidates.push(pdlss.duration.maxSessionDuration);\n if (pdlss.duration.ttl) candidates.push(pdlss.duration.ttl);\n if (candidates.length > 0) {\n transport.duration = { maxSessionDuration: Math.min(...candidates) };\n }\n }\n\n // Scope: use the primary jurisdiction\n if (pdlss.scope?.jurisdictions?.length) {\n transport.scope = { jurisdiction: pdlss.scope.jurisdictions[0] };\n }\n\n return transport;\n}\n\n/**\n * Parse transport format back into full PDLSS config.\n * Used by counterparty-side when receiving credentials.\n */\nexport function parsePDLSSFromTransport(transport: TransportPDLSS): PDLSSConfig {\n const pdlss: PDLSSConfig = {};\n\n if (transport.purpose) {\n pdlss.purpose = {\n categories: [transport.purpose.category],\n allowedActions: transport.purpose.action ? [transport.purpose.action] : undefined,\n };\n }\n\n if (transport.duration) {\n pdlss.duration = {\n maxSessionDuration: transport.duration.maxSessionDuration,\n };\n }\n\n if (transport.scope) {\n pdlss.scope = {\n jurisdictions: transport.scope.jurisdiction ? [transport.scope.jurisdiction] : undefined,\n };\n }\n\n return pdlss;\n}\n","/**\n * Decision Client — Counterparty-Side Decision Recording\n *\n * Helper for counterparties to record their grant/deny decisions\n * back to AstraSync after receiving a verification result.\n */\n\nimport type { GatewayConfig } from '../types';\n\ninterface RecordDecisionParams {\n sessionId: string;\n decision: 'granted' | 'denied';\n reason?: string;\n tokenIssued?: boolean;\n auditId?: string;\n}\n\ninterface RecordDecisionResult {\n recorded: boolean;\n blockchainTxHash?: string;\n}\n\n/**\n * Record a counterparty's grant/deny decision for a verification session.\n * POST to /agents/verify-access/:sessionId/decision\n */\nexport async function recordDecision(\n config: GatewayConfig,\n params: RecordDecisionParams,\n): Promise<RecordDecisionResult> {\n const { sessionId, ...body } = params;\n const baseUrl = config.apiBaseUrl.replace(/\\/$/, '');\n const url = `${baseUrl}/agents/verify-access/${encodeURIComponent(sessionId)}/decision`;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n }\n\n if (config.customHeaders) {\n Object.assign(headers, config.customHeaders);\n }\n\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'Unknown error');\n throw new Error(\n `Failed to record decision for session ${sessionId}: ${response.status} ${errorText}`,\n );\n }\n\n const result = await response.json();\n\n return {\n recorded: result.recorded ?? true,\n blockchainTxHash: result.blockchainTxHash,\n };\n}\n"],"mappings":"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","names":["decodeSdJwtSync","createHash","createHash"]}
1
+ {"version":3,"sources":["../../src/transport/http.ts","../../src/transport/a2a.ts","../../src/transport/mcp.ts","../../src/transport/rfc9421.ts","../../src/transport/rfc9421-verify.ts","../../src/transport/nonce-store.ts","../../src/transport/vi.ts","../../src/transport/stripe-webhook.ts","../../src/transport/ap2.ts","../../src/transport/mpp.ts","../../src/transport/mpp-verify.ts","../../src/transport/x402.ts","../../src/transport/vi-verify.ts","../../src/transport/registry/visa.ts","../../src/transport/index.ts","../../src/agent/errors.ts","../../src/agent/client.ts","../../src/agent/challenge-handler.ts","../../src/agent/pdlss-formatter.ts","../../src/agent/decision-client.ts"],"sourcesContent":["/**\n * HTTP Transport Adapter\n *\n * Maps AstraSync credentials to/from HTTP headers (X-Astra-* convention).\n */\n\nimport type { AstraSyncCredentials } from '../types';\n\nconst HEADER_PREFIX = 'X-Astra-';\n\n/**\n * Inject AstraSync credentials into HTTP headers.\n */\nexport function setHttpHeaders(\n headers: Record<string, string>,\n credentials: AstraSyncCredentials,\n): Record<string, string> {\n const result = { ...headers };\n\n result[`${HEADER_PREFIX}ID`] = credentials.agentId;\n\n if (credentials.verifyUrl) {\n result[`${HEADER_PREFIX}Verify`] = credentials.verifyUrl;\n }\n\n if (credentials.challengeUrl) {\n result[`${HEADER_PREFIX}Challenge`] = credentials.challengeUrl;\n }\n\n if (credentials.pdlss?.purpose) {\n const purposeValue = credentials.pdlss.purpose.action\n ? `${credentials.pdlss.purpose.category}:${credentials.pdlss.purpose.action}`\n : credentials.pdlss.purpose.category;\n result[`${HEADER_PREFIX}Purpose`] = purposeValue;\n }\n\n if (credentials.pdlss?.duration?.maxSessionDuration) {\n result[`${HEADER_PREFIX}Duration`] = String(credentials.pdlss.duration.maxSessionDuration);\n }\n\n if (credentials.pdlss?.scope?.jurisdiction) {\n result[`${HEADER_PREFIX}Scope`] = credentials.pdlss.scope.jurisdiction;\n }\n\n return result;\n}\n\n/**\n * Extract AstraSync credentials from HTTP headers.\n */\nexport function extractHttpCredentials(\n headers: Record<string, string | string[] | undefined>,\n): AstraSyncCredentials | null {\n const getValue = (key: string): string | undefined => {\n const v = headers[key] ?? headers[key.toLowerCase()];\n return Array.isArray(v) ? v[0] : v;\n };\n\n const agentId = getValue(`${HEADER_PREFIX}ID`) ?? getValue('x-astra-id');\n if (!agentId) return null;\n\n const credentials: AstraSyncCredentials = { agentId };\n\n const verifyUrl = getValue(`${HEADER_PREFIX}Verify`) ?? getValue('x-astra-verify');\n if (verifyUrl) credentials.verifyUrl = verifyUrl;\n\n const challengeUrl = getValue(`${HEADER_PREFIX}Challenge`) ?? getValue('x-astra-challenge');\n if (challengeUrl) credentials.challengeUrl = challengeUrl;\n\n const purpose = getValue(`${HEADER_PREFIX}Purpose`) ?? getValue('x-astra-purpose');\n if (purpose) {\n const [category, action] = purpose.split(':');\n credentials.pdlss = {\n ...credentials.pdlss,\n purpose: { category, action },\n };\n }\n\n const duration = getValue(`${HEADER_PREFIX}Duration`) ?? getValue('x-astra-duration');\n if (duration) {\n credentials.pdlss = {\n ...credentials.pdlss,\n duration: { maxSessionDuration: parseInt(duration, 10) },\n };\n }\n\n const scope = getValue(`${HEADER_PREFIX}Scope`) ?? getValue('x-astra-scope');\n if (scope) {\n credentials.pdlss = {\n ...credentials.pdlss,\n scope: { jurisdiction: scope },\n };\n }\n\n return credentials;\n}\n","/**\n * A2A (Agent-to-Agent) Transport Adapter\n *\n * Maps AstraSync credentials to/from A2A task metadata.astrasync block.\n */\n\nimport type { AstraSyncCredentials } from '../types';\n\ninterface A2ATask {\n metadata?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\ninterface AstraSyncMetadata {\n agentId: string;\n verifyUrl?: string;\n challengeUrl?: string;\n purpose?: { category: string; action?: string };\n duration?: { maxSessionDuration?: number };\n scope?: { jurisdiction?: string };\n}\n\n/**\n * Add AstraSync credentials to an A2A task's metadata block.\n */\nexport function setA2AMetadata(\n task: A2ATask,\n credentials: AstraSyncCredentials,\n): A2ATask {\n const astrasync: AstraSyncMetadata = {\n agentId: credentials.agentId,\n };\n\n if (credentials.verifyUrl) astrasync.verifyUrl = credentials.verifyUrl;\n if (credentials.challengeUrl) astrasync.challengeUrl = credentials.challengeUrl;\n if (credentials.pdlss?.purpose) astrasync.purpose = credentials.pdlss.purpose;\n if (credentials.pdlss?.duration) astrasync.duration = credentials.pdlss.duration;\n if (credentials.pdlss?.scope) astrasync.scope = credentials.pdlss.scope;\n\n return {\n ...task,\n metadata: {\n ...task.metadata,\n astrasync,\n },\n };\n}\n\n/**\n * Extract AstraSync credentials from an A2A task's metadata block.\n */\nexport function extractA2ACredentials(task: A2ATask): AstraSyncCredentials | null {\n const meta = task.metadata?.astrasync as AstraSyncMetadata | undefined;\n if (!meta?.agentId) return null;\n\n const credentials: AstraSyncCredentials = {\n agentId: meta.agentId,\n };\n\n if (meta.verifyUrl) credentials.verifyUrl = meta.verifyUrl;\n if (meta.challengeUrl) credentials.challengeUrl = meta.challengeUrl;\n\n if (meta.purpose || meta.duration || meta.scope) {\n credentials.pdlss = {};\n if (meta.purpose) credentials.pdlss.purpose = meta.purpose;\n if (meta.duration) credentials.pdlss.duration = meta.duration;\n if (meta.scope) credentials.pdlss.scope = meta.scope;\n }\n\n return credentials;\n}\n","/**\n * MCP (Model Context Protocol) Transport Adapter\n *\n * Maps AstraSync credentials to/from MCP params._meta.astrasync block.\n */\n\nimport type { AstraSyncCredentials } from '../types';\n\ninterface McpParams {\n _meta?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\ninterface AstraSyncMeta {\n agentId: string;\n verifyUrl?: string;\n challengeUrl?: string;\n purpose?: { category: string; action?: string };\n duration?: { maxSessionDuration?: number };\n scope?: { jurisdiction?: string };\n}\n\n/**\n * Add AstraSync credentials to MCP params' _meta block.\n */\nexport function setMcpMeta(\n params: McpParams,\n credentials: AstraSyncCredentials,\n): McpParams {\n const astrasync: AstraSyncMeta = {\n agentId: credentials.agentId,\n };\n\n if (credentials.verifyUrl) astrasync.verifyUrl = credentials.verifyUrl;\n if (credentials.challengeUrl) astrasync.challengeUrl = credentials.challengeUrl;\n if (credentials.pdlss?.purpose) astrasync.purpose = credentials.pdlss.purpose;\n if (credentials.pdlss?.duration) astrasync.duration = credentials.pdlss.duration;\n if (credentials.pdlss?.scope) astrasync.scope = credentials.pdlss.scope;\n\n return {\n ...params,\n _meta: {\n ...params._meta,\n astrasync,\n },\n };\n}\n\n/**\n * Extract AstraSync credentials from MCP params' _meta block.\n */\nexport function extractMcpCredentials(params: McpParams): AstraSyncCredentials | null {\n const meta = params._meta?.astrasync as AstraSyncMeta | undefined;\n if (!meta?.agentId) return null;\n\n const credentials: AstraSyncCredentials = {\n agentId: meta.agentId,\n };\n\n if (meta.verifyUrl) credentials.verifyUrl = meta.verifyUrl;\n if (meta.challengeUrl) credentials.challengeUrl = meta.challengeUrl;\n\n if (meta.purpose || meta.duration || meta.scope) {\n credentials.pdlss = {};\n if (meta.purpose) credentials.pdlss.purpose = meta.purpose;\n if (meta.duration) credentials.pdlss.duration = meta.duration;\n if (meta.scope) credentials.pdlss.scope = meta.scope;\n }\n\n return credentials;\n}\n","/**\n * RFC 9421 HTTP Message Signatures parser.\n *\n * Wraps `structured-headers` (transitive dep of http-message-signatures) to\n * parse the Signature-Input and Signature Dictionary headers per RFC 9421 §2.\n *\n * Produces structured metadata (kid, algorithm, covered components, tag,\n * created/expires/nonce, signature bytes) without verifying the signature —\n * verification lives in rfc9421-verify.ts.\n *\n * Shared by:\n * - Agent Pay (Mastercard) — kid resolves via Mastercard Agent Registry\n * - TAP (Visa) — kid resolves via Visa JWKS\n * - Web Bot Auth (generic transport substrate) — kid resolves via\n * /.well-known/http-message-signatures-directory\n */\n\nimport { parseDictionary } from 'structured-headers';\n\nexport interface RFC9421SignatureParams {\n /** The label identifying the signature in the Dictionary header (e.g. \"sig1\"). */\n label: string;\n /** Key ID used to look up the verifying key in the relevant registry. */\n kid: string;\n /** Algorithm declared in the Signature-Input params (e.g. \"ecdsa-p256-sha256\", \"ed25519\"). */\n alg?: string;\n /** Covered components, in order, per RFC 9421 §2.1. */\n covered: string[];\n /** Base64url-encoded signature bytes extracted from the paired Signature header. */\n signatureBase64: string;\n /** Unix seconds when the signature was created. */\n created?: number;\n /** Unix seconds when the signature expires. */\n expires?: number;\n /** Nonce (opaque string) for replay protection. */\n nonce?: string;\n /** Tag parameter. For Agent Pay/TAP this is \"browse\" or \"purchase\"; undefined otherwise. */\n tag?: 'browse' | 'purchase' | string;\n}\n\nexport interface ParsedRFC9421 {\n signatures: RFC9421SignatureParams[];\n}\n\n/**\n * Parse the RFC 9421 Signature-Input and Signature headers from a request or response.\n * Returns all signatures present (a single message may carry multiple labelled signatures).\n *\n * Returns null if either header is missing or malformed.\n */\nexport function parseRFC9421(\n headers: Record<string, string | string[] | undefined>\n): ParsedRFC9421 | null {\n const sigInput = readHeader(headers, 'signature-input');\n const sig = readHeader(headers, 'signature');\n if (!sigInput || !sig) return null;\n\n let inputDict;\n let sigDict;\n try {\n inputDict = parseDictionary(sigInput);\n sigDict = parseDictionary(sig);\n } catch {\n return null;\n }\n\n const signatures: RFC9421SignatureParams[] = [];\n\n for (const [label, entry] of inputDict) {\n // entry.value is the inner list of covered components; entry[1] is the params Map.\n const innerList = Array.isArray(entry)\n ? entry[0]\n : (entry as { value?: unknown; params?: unknown }).value;\n const params = Array.isArray(entry)\n ? entry[1]\n : (entry as { value?: unknown; params?: unknown }).params;\n if (!Array.isArray(innerList) || !params) continue;\n\n const covered: string[] = [];\n for (const item of innerList as Array<[unknown, Map<string, unknown>]>) {\n const [bare] = Array.isArray(item) ? item : [item];\n if (typeof bare === 'string') covered.push(bare);\n else if (bare && typeof bare === 'object' && 'toString' in bare) covered.push(String(bare));\n }\n\n const paramsMap = params as Map<string, unknown>;\n const kid = coerceString(paramsMap.get('keyid'));\n if (!kid) continue;\n\n const sigEntry = sigDict.get(label);\n if (!sigEntry) continue;\n\n const sigBare = Array.isArray(sigEntry) ? sigEntry[0] : (sigEntry as { value?: unknown }).value;\n const signatureBase64 = extractBase64(sigBare);\n if (!signatureBase64) continue;\n\n signatures.push({\n label,\n kid,\n alg: coerceString(paramsMap.get('alg')),\n covered,\n signatureBase64,\n created: coerceNumber(paramsMap.get('created')),\n expires: coerceNumber(paramsMap.get('expires')),\n nonce: coerceString(paramsMap.get('nonce')),\n tag: coerceString(paramsMap.get('tag')),\n });\n }\n\n if (signatures.length === 0) return null;\n return { signatures };\n}\n\nfunction readHeader(\n headers: Record<string, string | string[] | undefined>,\n name: string\n): string | null {\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === name) {\n const raw = headers[key];\n if (typeof raw === 'string') return raw;\n if (Array.isArray(raw)) return raw.join(', ');\n return null;\n }\n }\n return null;\n}\n\nfunction coerceString(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (value == null) return undefined;\n if (typeof value === 'object' && 'toString' in (value as object)) {\n const s = String(value);\n return s.length > 0 ? s : undefined;\n }\n return undefined;\n}\n\nfunction coerceNumber(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n if (typeof value === 'bigint') return Number(value);\n return undefined;\n}\n\nfunction extractBase64(value: unknown): string | null {\n if (value instanceof Uint8Array) return bufferToBase64(value);\n if (value instanceof ArrayBuffer) return bufferToBase64(new Uint8Array(value));\n if (ArrayBuffer.isView(value)) {\n const v = value as ArrayBufferView;\n return bufferToBase64(new Uint8Array(v.buffer, v.byteOffset, v.byteLength));\n }\n if (typeof value === 'string') {\n if (value.startsWith(':') && value.endsWith(':')) return value.slice(1, -1);\n return value;\n }\n return null;\n}\n\nfunction bufferToBase64(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64');\n}\n","/**\n * RFC 9421 HTTP Message Signatures verification.\n *\n * Wraps http-message-signatures (dhensby) verifyMessage() with a RegistryResolver\n * hook for kid → JWK lookup. Library handles canonicalization + ES256/EdDSA/\n * HMAC/RSA verification; we supply the key-finding callback and policy around\n * clock skew.\n *\n * Shared by:\n * - Agent Pay (Mastercard) — resolver = createMastercardRegistry\n * - TAP (Visa) — resolver = createVisaRegistry\n * - Web Bot Auth (generic) — resolver = createWebBotAuthRegistry\n */\n\nimport { httpbis, type VerifierFinder, type VerifyingKey } from 'http-message-signatures';\nimport type { JWK } from 'jose';\nimport type { RegistryResolver } from './registry/types';\nimport { defaultNonceStore, type NonceStore } from './nonce-store';\n\nexport interface RFC9421VerifyRequest {\n method: string;\n url: string;\n headers: Record<string, string | string[]>;\n body?: string;\n}\n\nexport interface RFC9421VerifyOptions {\n resolver: RegistryResolver;\n /** Seconds of tolerance around created/expires. Default 60 (audit F-A1-05 tightening from 300). */\n clockSkewSec?: number;\n /** Injectable for deterministic tests. */\n now?: () => number;\n /** Optional replay-protection store. Defaults to in-process LRU. Audit F-A1-05. */\n nonceStore?: NonceStore;\n}\n\nexport interface RFC9421VerifyResult {\n ok: boolean;\n kid?: string;\n registry?: RegistryResolver['name'];\n algorithm?: string;\n error?: string;\n}\n\nexport async function verifyRFC9421(\n request: RFC9421VerifyRequest,\n options: RFC9421VerifyOptions\n): Promise<RFC9421VerifyResult> {\n const { resolver } = options;\n const tolerance = options.clockSkewSec ?? 60;\n const nowSec = options.now ? options.now() : Math.floor(Date.now() / 1000);\n const nonceStore = options.nonceStore ?? defaultNonceStore;\n\n let resolvedKid: string | undefined;\n let resolvedAlg: string | undefined;\n let replayDetected = false;\n\n const keyLookup: VerifierFinder = async (parameters) => {\n const kid = typeof parameters.keyid === 'string' ? parameters.keyid : undefined;\n if (!kid) return null;\n resolvedKid = kid;\n const alg = typeof parameters.alg === 'string' ? parameters.alg : undefined;\n if (alg) resolvedAlg = alg;\n\n const origin = safeOrigin(request.url);\n const jwk = await resolver.resolve(kid, { origin, algorithm: alg });\n if (!jwk) return null;\n\n // Check clock-skew on this specific signature's created/expires.\n // SignatureParameters may carry Date, number, or ISO string per library.\n const created = toUnixSeconds(parameters.created);\n const expires = toUnixSeconds(parameters.expires);\n if (created !== undefined && Math.abs(nowSec - created) > tolerance) return null;\n if (expires !== undefined && nowSec > expires + tolerance) return null;\n\n // Audit F-A1-05: replay protection. RFC 9421 signatures include a\n // `nonce` parameter (optional but recommended for replay-sensitive\n // contexts). When present, register kid+nonce; reject duplicates\n // within the expiry window.\n const nonce = typeof parameters.nonce === 'string' ? parameters.nonce : undefined;\n if (nonce) {\n const expiresAtMs = (expires !== undefined ? expires + tolerance : nowSec + tolerance) * 1000;\n if (nonceStore.seen(`rfc9421:${kid}:${nonce}`, expiresAtMs)) {\n replayDetected = true;\n return null;\n }\n }\n\n return jwkToVerifyingKey(kid, jwk, alg);\n };\n\n try {\n const result = await httpbis.verifyMessage(\n {\n keyLookup,\n },\n normalizeRequest(request)\n );\n if (result === true) {\n return {\n ok: true,\n kid: resolvedKid,\n registry: resolver.name,\n algorithm: resolvedAlg,\n };\n }\n return {\n ok: false,\n kid: resolvedKid,\n registry: resolver.name,\n algorithm: resolvedAlg,\n error: replayDetected\n ? 'RFC9421 signature replay — already seen within tolerance window'\n : result === false\n ? 'signature invalid'\n : 'no signature found',\n };\n } catch (err) {\n return {\n ok: false,\n kid: resolvedKid,\n registry: resolver.name,\n algorithm: resolvedAlg,\n error: err instanceof Error ? err.message : 'verification error',\n };\n }\n}\n\nfunction normalizeRequest(request: RFC9421VerifyRequest): {\n method: string;\n url: string;\n headers: Record<string, string | string[]>;\n} {\n return {\n method: request.method.toUpperCase(),\n url: request.url,\n headers: request.headers,\n };\n}\n\nfunction safeOrigin(url: string): string | undefined {\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\nasync function jwkToVerifyingKey(\n id: string,\n jwk: JWK,\n alg: string | undefined\n): Promise<VerifyingKey> {\n const algorithm = alg ?? inferAlgFromJwk(jwk);\n const { subtle } = await getCrypto();\n const importAlg = webCryptoImportAlgFor(algorithm);\n const verifyAlg = webCryptoAlgFor(algorithm);\n if (!importAlg || !verifyAlg) {\n return {\n id,\n algs: alg ? [alg] : undefined,\n verify: async () => false,\n };\n }\n const key = await subtle.importKey('jwk', jwk as JsonWebKey, importAlg, false, ['verify']);\n\n return {\n id,\n algs: alg ? [alg] : undefined,\n verify: async (data: Buffer, signature: Buffer): Promise<boolean> => {\n try {\n return await subtle.verify(verifyAlg, key, toArrayBuffer(signature), toArrayBuffer(data));\n } catch {\n return false;\n }\n },\n };\n}\n\nfunction inferAlgFromJwk(jwk: JWK): string {\n if (jwk.kty === 'OKP' && jwk.crv === 'Ed25519') return 'ed25519';\n if (jwk.kty === 'EC' && jwk.crv === 'P-256') return 'ecdsa-p256-sha256';\n if (jwk.kty === 'EC' && jwk.crv === 'P-384') return 'ecdsa-p384-sha384';\n if (jwk.kty === 'RSA') return 'rsa-v1_5-sha256';\n return 'ecdsa-p256-sha256';\n}\n\nfunction webCryptoAlgFor(\n rfc9421Alg: string\n): AlgorithmIdentifier | EcdsaParams | RsaPssParams | null {\n switch (rfc9421Alg) {\n case 'ed25519':\n return { name: 'Ed25519' };\n case 'ecdsa-p256-sha256':\n return { name: 'ECDSA', hash: 'SHA-256' };\n case 'ecdsa-p384-sha384':\n return { name: 'ECDSA', hash: 'SHA-384' };\n case 'rsa-v1_5-sha256':\n return { name: 'RSASSA-PKCS1-v1_5' };\n case 'rsa-pss-sha512':\n return { name: 'RSA-PSS', saltLength: 64 };\n default:\n return null;\n }\n}\n\nfunction webCryptoImportAlgFor(\n rfc9421Alg: string\n): AlgorithmIdentifier | EcKeyImportParams | RsaHashedImportParams | null {\n switch (rfc9421Alg) {\n case 'ed25519':\n return { name: 'Ed25519' };\n case 'ecdsa-p256-sha256':\n return { name: 'ECDSA', namedCurve: 'P-256' };\n case 'ecdsa-p384-sha384':\n return { name: 'ECDSA', namedCurve: 'P-384' };\n case 'rsa-v1_5-sha256':\n return { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' };\n case 'rsa-pss-sha512':\n return { name: 'RSA-PSS', hash: 'SHA-512' };\n default:\n return null;\n }\n}\n\nfunction toArrayBuffer(buf: Buffer): ArrayBuffer {\n const out = new ArrayBuffer(buf.byteLength);\n new Uint8Array(out).set(buf);\n return out;\n}\n\nfunction toUnixSeconds(v: unknown): number | undefined {\n if (typeof v === 'number' && Number.isFinite(v)) return v;\n if (v instanceof Date) return Math.floor(v.getTime() / 1000);\n if (typeof v === 'string') {\n const parsed = Date.parse(v);\n if (Number.isFinite(parsed)) return Math.floor(parsed / 1000);\n }\n return undefined;\n}\n\nasync function getCrypto(): Promise<{ subtle: SubtleCrypto }> {\n if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.subtle) {\n return { subtle: globalThis.crypto.subtle };\n }\n // Node fallback\n const nodeCrypto = await import('node:crypto');\n return { subtle: nodeCrypto.webcrypto.subtle as SubtleCrypto };\n}\n","/**\n * Shared nonce/signature replay-protection store for transport verifiers.\n *\n * Audit F-A1-05: every transport-signature verifier (RFC9421, VI, AP2, ACP,\n * MPP) validates a created/expires window but none consult a seen-nonce\n * cache. Any captured signed request can be replayed within the (default\n * 300s, now tightened to 60s) tolerance window.\n *\n * This module ships a bounded in-memory LRU as the default. Production\n * deployments with multi-pod horizontal scaling SHOULD pass a shared store\n * (Redis-backed) via the verifier options to make replay protection global\n * rather than per-pod.\n *\n * The store interface is intentionally minimal: a single `seen(key,\n * expiresAt)` method that returns true iff the key was already recorded\n * (i.e. caller should reject as a replay). Callers compose the key from\n * whichever identifiers are unique to the signature (kid + nonce + sig\n * digest, typically).\n */\n\nexport interface NonceStore {\n /**\n * Record `key` as seen. Returns true iff the key was ALREADY present —\n * i.e. caller should reject the request as a replay. Returns false on\n * first sighting (caller should proceed).\n *\n * `expiresAtMs` is a hint for the store to evict entries that can no\n * longer cause harm (their signature window has elapsed).\n */\n seen(key: string, expiresAtMs: number): boolean;\n}\n\n/**\n * Default in-memory LRU. Per-process, lost on restart. Suitable for\n * single-pod deploys and for development; production multi-pod deploys\n * should pass a shared store.\n *\n * Capacity-bounded: once the limit is reached, the oldest entry is dropped\n * even if it hasn't yet expired. The limit (default 10k entries) is large\n * enough that meaningful replay windows shouldn't approach it; if a\n * deployment is hitting it, that's a signal to migrate to Redis.\n *\n * Also runs a lazy sweep on each `seen` call to evict entries past their\n * `expiresAtMs` — keeps memory bounded under burst load.\n */\nexport class InMemoryNonceStore implements NonceStore {\n private readonly entries = new Map<string, number>();\n private readonly capacity: number;\n private lastSweepMs = 0;\n\n constructor(capacity = 10_000) {\n this.capacity = capacity;\n }\n\n seen(key: string, expiresAtMs: number): boolean {\n const nowMs = Date.now();\n\n // Lazy sweep: at most once per second, walk expired entries.\n if (nowMs - this.lastSweepMs > 1000) {\n for (const [k, exp] of this.entries) {\n if (exp <= nowMs) this.entries.delete(k);\n }\n this.lastSweepMs = nowMs;\n }\n\n const existing = this.entries.get(key);\n if (existing !== undefined && existing > nowMs) {\n return true; // replay\n }\n\n // First sighting (or stale entry) — record + evict oldest if over capacity.\n if (this.entries.size >= this.capacity) {\n const oldest = this.entries.keys().next().value;\n if (oldest !== undefined) this.entries.delete(oldest);\n }\n this.entries.set(key, expiresAtMs);\n return false;\n }\n}\n\n/**\n * Process-wide singleton instance for convenience. Verifiers that don't\n * receive an explicit nonceStore in their options fall back to this.\n *\n * Memory-bounded at 10k entries by default; sufficient for a single pod\n * handling thousands of requests/sec where signatures only carry value\n * within their (60s default) expiry window.\n */\nexport const defaultNonceStore = new InMemoryNonceStore();\n","/**\n * VI (Verifiable Intent) SD-JWT extraction.\n *\n * Open-sourced 5 March 2026 by Mastercard + Google (v0.1-draft).\n * VI is a 3-layer SD-JWT chain:\n * L1 — issuer → wallet (credential provider)\n * L2 — user → agent (cnf.jwk binding to L3 agent key)\n * L3 — agent → merchant (payment or checkout mandate, split into L3a / L3b\n * cross-referenced via transaction_id)\n *\n * This module does EXTRACTION ONLY — it decodes SD-JWT structure and pulls\n * out the mandate type, kid, executionMode, 8 constraint types, checkoutHash\n * (constraint type 8), transactionId, and raw layers for later verification.\n *\n * Signature verification lives in vi-verify.ts; this module uses @sd-jwt's\n * sync decoder with a SHA-256 hasher for structural parsing only.\n */\n\nimport { splitSdJwt, decodeSdJwtSync } from '@sd-jwt/decode';\nimport { createHash } from 'node:crypto';\nimport type { VIMandateType } from './purpose-mapping';\n\nexport type { VIMandateType };\nexport type VIExecutionMode = 'Immediate' | 'Autonomous' | 'Both';\n\nexport interface VIAllowedParty {\n id?: string;\n name?: string;\n website?: string;\n}\n\nexport interface VILineItem {\n id?: string;\n acceptableItems?: string[];\n quantity?: number;\n}\n\nexport interface VIPaymentAmount {\n currency?: string;\n min?: number;\n max?: number;\n}\n\nexport interface VIBudgetLimit {\n currency?: string;\n max?: number;\n}\n\nexport interface VIRecurrence {\n frequency?: string;\n startDate?: string;\n endDate?: string;\n maxOccurrences?: number;\n}\n\nexport interface VIConstraints {\n allowedMerchants?: VIAllowedParty[];\n allowedPayees?: VIAllowedParty[];\n lineItems?: VILineItem[];\n paymentAmount?: VIPaymentAmount;\n budgetLimit?: VIBudgetLimit;\n recurrence?: VIRecurrence;\n agentRecurrence?: VIRecurrence;\n}\n\nexport interface VIExtractedClaims {\n mandateType: VIMandateType;\n kid?: string;\n executionMode?: VIExecutionMode;\n credentialProvider?: string;\n constraints: VIConstraints;\n /** VI constraint type 8 — SHA-256 of the paired L2 checkout disclosure. */\n checkoutHash?: string;\n transactionId?: string;\n rawLayers: { l1?: string; l2?: string; l3?: string };\n}\n\n/**\n * Extract VI claims from a compact SD-JWT string.\n *\n * Input shape:\n * <jwt>~<disclosure1>~<disclosure2>~...~<kbJwt?>\n *\n * Returns null if parsing fails at any layer. Does not verify signatures.\n */\nexport function extractVIClaims(sdJwtCompact: string): VIExtractedClaims | null {\n if (!sdJwtCompact || typeof sdJwtCompact !== 'string') return null;\n\n let decoded;\n try {\n decoded = decodeSdJwtSync(sdJwtCompact, sha256Sync);\n } catch {\n return null;\n }\n\n const split = safeSplit(sdJwtCompact);\n\n const payload = (decoded.jwt?.payload ?? {}) as Record<string, unknown>;\n const disclosures = decoded.disclosures ?? [];\n\n // Apply disclosures onto payload to resolve _sd references.\n // Disclosure from @sd-jwt/utils has { key, value, digest() } where digest is\n // a function — we only need key+value here, so narrow via a structural cast.\n const claims = applyDisclosures(\n payload,\n disclosures as unknown as Array<{ key?: string; value?: unknown }>\n );\n\n const mandateType = coerceMandateType(\n claims.mandate_type ?? claims.mandateType ?? payload.mandate_type ?? payload.mandateType\n );\n if (!mandateType) return null;\n\n const kid = coerceString(\n (decoded.jwt?.header as Record<string, unknown> | undefined)?.kid ?? claims.kid ?? payload.kid\n );\n\n const executionMode = coerceExecutionMode(claims.execution_mode ?? claims.executionMode);\n const credentialProvider = coerceString(claims.iss ?? payload.iss);\n\n const constraints = extractConstraints(\n (claims.constraints ?? claims.default_constraints ?? {}) as Record<string, unknown>\n );\n\n const transactionId = coerceString(claims.transaction_id ?? claims.transactionId);\n const checkoutHash = coerceString(\n claims.checkout_hash ??\n claims.conditional_transaction_id ??\n (claims.payment_reference as Record<string, unknown> | undefined)?.checkout_hash\n );\n\n return {\n mandateType,\n kid,\n executionMode,\n credentialProvider,\n constraints,\n checkoutHash,\n transactionId,\n rawLayers: split,\n };\n}\n\nfunction safeSplit(compact: string): { l1?: string; l2?: string; l3?: string } {\n try {\n const { jwt, kbJwt } = splitSdJwt(compact);\n // VI layering maps loosely: the outer JWT is L3 (agent mandate), KB-JWT\n // (if present) is the key-binding proof, and disclosures carry L2/L1 fragments.\n return { l3: jwt, l2: kbJwt };\n } catch {\n return {};\n }\n}\n\nfunction applyDisclosures(\n payload: Record<string, unknown>,\n disclosures: Array<{ key?: string; value?: unknown }>\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...payload };\n for (const d of disclosures) {\n if (d.key && d.value !== undefined && !(d.key in result)) {\n result[d.key] = d.value;\n }\n }\n return result;\n}\n\nfunction extractConstraints(raw: Record<string, unknown>): VIConstraints {\n return {\n allowedMerchants: toAllowedPartyArray(raw.allowed_merchants ?? raw.allowedMerchants),\n allowedPayees: toAllowedPartyArray(raw.allowed_payees ?? raw.allowedPayees),\n lineItems: toLineItemArray(raw.line_items ?? raw.lineItems),\n paymentAmount: toPaymentAmount(raw.payment_amount ?? raw.paymentAmount),\n budgetLimit: toBudgetLimit(raw.budget_limit ?? raw.budgetLimit ?? raw.budget),\n recurrence: toRecurrence(raw.recurrence),\n agentRecurrence: toRecurrence(raw.agent_recurrence ?? raw.agentRecurrence),\n };\n}\n\nfunction toAllowedPartyArray(v: unknown): VIAllowedParty[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out: VIAllowedParty[] = [];\n for (const item of v) {\n if (item && typeof item === 'object') {\n const r = item as Record<string, unknown>;\n out.push({\n id: coerceString(r.id),\n name: coerceString(r.name),\n website: coerceString(r.website),\n });\n }\n }\n return out.length > 0 ? out : undefined;\n}\n\nfunction toLineItemArray(v: unknown): VILineItem[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out: VILineItem[] = [];\n for (const item of v) {\n if (item && typeof item === 'object') {\n const r = item as Record<string, unknown>;\n const acc = r.acceptable_items ?? r.acceptableItems;\n out.push({\n id: coerceString(r.id),\n acceptableItems: Array.isArray(acc)\n ? (acc.filter((a) => typeof a === 'string') as string[])\n : undefined,\n quantity: coerceNumber(r.quantity),\n });\n }\n }\n return out.length > 0 ? out : undefined;\n}\n\nfunction toPaymentAmount(v: unknown): VIPaymentAmount | undefined {\n if (!v || typeof v !== 'object') return undefined;\n const r = v as Record<string, unknown>;\n return {\n currency: coerceString(r.currency),\n min: coerceNumber(r.min),\n max: coerceNumber(r.max),\n };\n}\n\nfunction toBudgetLimit(v: unknown): VIBudgetLimit | undefined {\n if (!v || typeof v !== 'object') return undefined;\n const r = v as Record<string, unknown>;\n return {\n currency: coerceString(r.currency),\n max: coerceNumber(r.max),\n };\n}\n\nfunction toRecurrence(v: unknown): VIRecurrence | undefined {\n if (!v || typeof v !== 'object') return undefined;\n const r = v as Record<string, unknown>;\n return {\n frequency: coerceString(r.frequency),\n startDate: coerceString(r.start_date ?? r.startDate),\n endDate: coerceString(r.end_date ?? r.endDate),\n maxOccurrences: coerceNumber(r.max_occurrences ?? r.maxOccurrences),\n };\n}\n\nfunction coerceMandateType(v: unknown): VIMandateType | null {\n if (v === 'checkout' || v === 'payment' || v === 'checkout.open' || v === 'payment.open') {\n return v;\n }\n return null;\n}\n\nfunction coerceExecutionMode(v: unknown): VIExecutionMode | undefined {\n return v === 'Immediate' || v === 'Autonomous' || v === 'Both' ? v : undefined;\n}\n\nfunction coerceString(v: unknown): string | undefined {\n return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n\nfunction coerceNumber(v: unknown): number | undefined {\n if (typeof v === 'number' && Number.isFinite(v)) return v;\n if (typeof v === 'string') {\n const n = Number(v);\n return Number.isFinite(n) ? n : undefined;\n }\n return undefined;\n}\n\nfunction sha256Sync(data: string | ArrayBuffer): Uint8Array {\n const buf =\n typeof data === 'string' ? Buffer.from(data, 'utf-8') : Buffer.from(new Uint8Array(data));\n const hash = createHash('sha256').update(buf).digest();\n return new Uint8Array(hash.buffer, hash.byteOffset, hash.byteLength);\n}\n","/**\n * Stripe webhook HMAC-SHA256 verifier (inline).\n *\n * Stripe-Signature header format: \"t=TIMESTAMP,v1=HEX_SIGNATURE\"\n * - t: unix seconds when Stripe signed the webhook\n * - v1: HMAC-SHA256(webhook_secret, `${t}.${payload}`) as hex\n *\n * Multiple v1 signatures can coexist during secret rotation; any match wins.\n * Default tolerance on timestamp age: 300s (matches Stripe's own default).\n *\n * Documented at docs.stripe.com — we intentionally inline ~25 LOC rather\n * than pull in the full stripe npm package (MIT but 600KB+ with deps).\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\nexport interface VerifyStripeWebhookResult {\n ok: boolean;\n timestamp?: number;\n error?: string;\n}\n\nexport interface VerifyStripeWebhookOptions {\n toleranceSec?: number;\n /** Injectable for deterministic tests. */\n now?: () => number;\n}\n\nexport function verifyStripeWebhook(\n payload: string,\n signatureHeader: string | undefined,\n secret: string,\n options: VerifyStripeWebhookOptions = {}\n): VerifyStripeWebhookResult {\n if (!signatureHeader) return { ok: false, error: 'missing Stripe-Signature header' };\n if (!secret) return { ok: false, error: 'missing webhook secret' };\n\n const parsed = parseStripeSignature(signatureHeader);\n if (!parsed.timestamp) return { ok: false, error: 'malformed Stripe-Signature (missing t=)' };\n if (parsed.v1Signatures.length === 0) {\n return { ok: false, error: 'malformed Stripe-Signature (no v1=)' };\n }\n\n const tolerance = options.toleranceSec ?? 300;\n const now = options.now ? options.now() : Math.floor(Date.now() / 1000);\n if (Math.abs(now - parsed.timestamp) > tolerance) {\n return {\n ok: false,\n timestamp: parsed.timestamp,\n error: `timestamp outside tolerance (${tolerance}s)`,\n };\n }\n\n const signedPayload = `${parsed.timestamp}.${payload}`;\n const expected = createHmac('sha256', secret).update(signedPayload).digest();\n\n for (const candidateHex of parsed.v1Signatures) {\n const candidate = hexToBuffer(candidateHex);\n if (!candidate) continue;\n if (candidate.length !== expected.length) continue;\n if (timingSafeEqual(candidate, expected)) {\n return { ok: true, timestamp: parsed.timestamp };\n }\n }\n\n return { ok: false, timestamp: parsed.timestamp, error: 'signature mismatch' };\n}\n\ninterface ParsedStripeSignature {\n timestamp: number | null;\n v1Signatures: string[];\n}\n\nfunction parseStripeSignature(header: string): ParsedStripeSignature {\n let timestamp: number | null = null;\n const v1Signatures: string[] = [];\n for (const part of header.split(',')) {\n const [rawKey, rawValue] = part.split('=');\n if (!rawKey || !rawValue) continue;\n const key = rawKey.trim();\n const value = rawValue.trim();\n if (key === 't') {\n const n = Number(value);\n if (Number.isFinite(n)) timestamp = n;\n } else if (key === 'v1') {\n v1Signatures.push(value);\n }\n }\n return { timestamp, v1Signatures };\n}\n\nfunction hexToBuffer(hex: string): Buffer | null {\n if (!/^[0-9a-fA-F]+$/.test(hex) || hex.length % 2 !== 0) return null;\n return Buffer.from(hex, 'hex');\n}\n","/**\n * AP2 (Agent Payments Protocol) mandate extraction.\n *\n * Google-led, launched 3 April 2026 with 60+ partners (Mastercard, PayPal,\n * Coinbase, AmEx, Revolut, UnionPay, ...). AP2 ships three mandate types as\n * SD-JWTs in series:\n * - intent_mandate — user declares intent (amount, merchant category, etc.)\n * - cart_mandate — user approves a cart (specific items, totals)\n * - payment_mandate — authorizes the actual payment rail\n *\n * Mandates are cross-referenced via ids; each is an SD-JWT over ES256 (or\n * equivalent). We decode via @sd-jwt/decode and extract the AP2-specific\n * shape — verification lives in ap2-verify.ts.\n */\n\nimport { decodeSdJwtSync } from '@sd-jwt/decode';\nimport { createHash } from 'node:crypto';\nimport type { AP2MandateType } from './purpose-mapping';\n\nexport type { AP2MandateType };\n\nexport interface AP2PaymentDetailsTotal {\n amount?: { value?: string | number; currency?: string };\n label?: string;\n}\n\nexport interface AP2IntentMandateClaims {\n type: 'intent_mandate';\n agent_id?: string;\n user_id?: string;\n merchant_category?: string;\n allowedMerchantDomains?: string[];\n paymentMethods?: string[];\n expires?: string;\n payment_details_total?: AP2PaymentDetailsTotal;\n raw: Record<string, unknown>;\n}\n\nexport interface AP2CartMandateClaims {\n type: 'cart_mandate';\n agent_id?: string;\n intent_mandate_id?: string;\n merchant_id?: string;\n line_items?: Array<{\n id?: string;\n quantity?: number;\n price?: { value?: string | number; currency?: string };\n }>;\n payment_details_total?: AP2PaymentDetailsTotal;\n expires?: string;\n raw: Record<string, unknown>;\n}\n\nexport interface AP2PaymentMandateClaims {\n type: 'payment_mandate';\n agent_id?: string;\n cart_mandate_id?: string;\n payment_method?: string;\n payment_details_total?: AP2PaymentDetailsTotal;\n credential_provider?: string;\n raw: Record<string, unknown>;\n}\n\nexport type AP2MandateClaims =\n | AP2IntentMandateClaims\n | AP2CartMandateClaims\n | AP2PaymentMandateClaims;\n\nexport interface AP2MandateTriple {\n intent?: AP2IntentMandateClaims;\n cart?: AP2CartMandateClaims;\n payment?: AP2PaymentMandateClaims;\n rawLayers: { intentJwt?: string; cartJwt?: string; paymentJwt?: string };\n}\n\n/**\n * Extract a single AP2 mandate from a compact SD-JWT.\n * Returns null if the SD-JWT is malformed or lacks a recognized type field.\n */\nexport function extractAP2Mandate(sdJwtCompact: string): AP2MandateClaims | null {\n if (!sdJwtCompact || typeof sdJwtCompact !== 'string') return null;\n\n let decoded;\n try {\n decoded = decodeSdJwtSync(sdJwtCompact, sha256Sync);\n } catch {\n return null;\n }\n\n const payload = (decoded.jwt?.payload ?? {}) as Record<string, unknown>;\n const disclosures = decoded.disclosures ?? [];\n const claims = applyDisclosures(\n payload,\n disclosures as unknown as Array<{ key?: string; value?: unknown }>\n );\n\n const type = coerceMandateType(claims.type ?? claims.mandate_type ?? claims.mandateType);\n if (!type) return null;\n\n if (type === 'intent_mandate') return buildIntent(claims);\n if (type === 'cart_mandate') return buildCart(claims);\n return buildPayment(claims);\n}\n\nexport interface AP2MandateTripleInput {\n intent?: string;\n cart?: string;\n payment?: string;\n}\n\n/**\n * Extract an intent / cart / payment triple, returning whichever are present.\n * Does NOT enforce cross-reference consistency — that's ap2-verify.ts's job.\n */\nexport function extractAP2Mandates(input: AP2MandateTripleInput): AP2MandateTriple {\n const intent = input.intent\n ? (extractAP2Mandate(input.intent) as AP2IntentMandateClaims | null)\n : null;\n const cart = input.cart ? (extractAP2Mandate(input.cart) as AP2CartMandateClaims | null) : null;\n const payment = input.payment\n ? (extractAP2Mandate(input.payment) as AP2PaymentMandateClaims | null)\n : null;\n return {\n intent: intent ?? undefined,\n cart: cart ?? undefined,\n payment: payment ?? undefined,\n rawLayers: {\n intentJwt: input.intent,\n cartJwt: input.cart,\n paymentJwt: input.payment,\n },\n };\n}\n\nfunction buildIntent(claims: Record<string, unknown>): AP2IntentMandateClaims {\n return {\n type: 'intent_mandate',\n agent_id: coerceString(claims.agent_id ?? claims.agentId),\n user_id: coerceString(claims.user_id ?? claims.userId ?? claims.sub),\n merchant_category: coerceString(claims.merchant_category ?? claims.merchantCategory),\n allowedMerchantDomains: toStringArray(\n claims.allowed_merchant_domains ?? claims.allowedMerchantDomains\n ),\n paymentMethods: toStringArray(claims.payment_methods ?? claims.paymentMethods),\n expires: coerceString(claims.expires ?? claims.exp),\n payment_details_total: toPaymentDetails(claims.payment_details_total ?? claims.total),\n raw: claims,\n };\n}\n\nfunction buildCart(claims: Record<string, unknown>): AP2CartMandateClaims {\n return {\n type: 'cart_mandate',\n agent_id: coerceString(claims.agent_id ?? claims.agentId),\n intent_mandate_id: coerceString(claims.intent_mandate_id ?? claims.intentMandateId),\n merchant_id: coerceString(claims.merchant_id ?? claims.merchantId),\n line_items: toLineItems(claims.line_items ?? claims.lineItems),\n payment_details_total: toPaymentDetails(claims.payment_details_total ?? claims.total),\n expires: coerceString(claims.expires ?? claims.exp),\n raw: claims,\n };\n}\n\nfunction buildPayment(claims: Record<string, unknown>): AP2PaymentMandateClaims {\n return {\n type: 'payment_mandate',\n agent_id: coerceString(claims.agent_id ?? claims.agentId),\n cart_mandate_id: coerceString(claims.cart_mandate_id ?? claims.cartMandateId),\n payment_method: coerceString(claims.payment_method ?? claims.paymentMethod),\n payment_details_total: toPaymentDetails(claims.payment_details_total ?? claims.total),\n credential_provider: coerceString(claims.credential_provider ?? claims.credentialProvider),\n raw: claims,\n };\n}\n\nfunction toPaymentDetails(v: unknown): AP2PaymentDetailsTotal | undefined {\n if (!v || typeof v !== 'object') return undefined;\n const r = v as Record<string, unknown>;\n const amount = r.amount as Record<string, unknown> | undefined;\n return {\n amount: amount\n ? {\n value: coerceStringOrNumber(amount.value),\n currency: coerceString(amount.currency),\n }\n : undefined,\n label: coerceString(r.label),\n };\n}\n\nfunction toLineItems(v: unknown): AP2CartMandateClaims['line_items'] {\n if (!Array.isArray(v)) return undefined;\n const items: NonNullable<AP2CartMandateClaims['line_items']> = [];\n for (const item of v) {\n if (!item || typeof item !== 'object') continue;\n const r = item as Record<string, unknown>;\n const price = r.price as Record<string, unknown> | undefined;\n items.push({\n id: coerceString(r.id),\n quantity: coerceNumber(r.quantity),\n price: price\n ? {\n value: coerceStringOrNumber(price.value),\n currency: coerceString(price.currency),\n }\n : undefined,\n });\n }\n return items.length > 0 ? items : undefined;\n}\n\nfunction toStringArray(v: unknown): string[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out = v.filter((i): i is string => typeof i === 'string' && i.length > 0);\n return out.length > 0 ? out : undefined;\n}\n\nfunction applyDisclosures(\n payload: Record<string, unknown>,\n disclosures: Array<{ key?: string; value?: unknown }>\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...payload };\n for (const d of disclosures) {\n if (d.key && d.value !== undefined && !(d.key in result)) {\n result[d.key] = d.value;\n }\n }\n return result;\n}\n\nfunction coerceMandateType(v: unknown): AP2MandateType | null {\n if (v === 'intent_mandate' || v === 'cart_mandate' || v === 'payment_mandate') return v;\n return null;\n}\n\nfunction coerceString(v: unknown): string | undefined {\n return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n\nfunction coerceNumber(v: unknown): number | undefined {\n if (typeof v === 'number' && Number.isFinite(v)) return v;\n if (typeof v === 'string') {\n const n = Number(v);\n return Number.isFinite(n) ? n : undefined;\n }\n return undefined;\n}\n\nfunction coerceStringOrNumber(v: unknown): string | number | undefined {\n if (typeof v === 'number' && Number.isFinite(v)) return v;\n if (typeof v === 'string' && v.length > 0) return v;\n return undefined;\n}\n\nfunction sha256Sync(data: string | ArrayBuffer): Uint8Array {\n const buf =\n typeof data === 'string' ? Buffer.from(data, 'utf-8') : Buffer.from(new Uint8Array(data));\n const hash = createHash('sha256').update(buf).digest();\n return new Uint8Array(hash.buffer, hash.byteOffset, hash.byteLength);\n}\n","/**\n * MPP (Machine Payments Protocol) extractor.\n *\n * Wraps mppx (wevm) — pinned to 0.5.13, wrapped behind this adapter so\n * upgrades localise here. MPP launched March 18 2026 (Stripe + Tempo +\n * Paradigm), IETF draft-ryan-httpauth-payment-01.\n *\n * Flow:\n * Client → GET /resource\n * Server → 402 + WWW-Authenticate: Payment id=..., realm=..., method=tempo|stripe|...\n * Client → GET /resource with Authorization: Payment <base64url-json credential>\n * Server → 200 + Payment-Receipt: <base64url-json receipt>\n *\n * What we extract:\n * - Challenge: id, realm, method, intent, request{amount,currency,...}, expires, digest\n * - Credential: challenge + source (DID/chain-key) + payload (method-specific)\n * - Receipt: challengeId, method, reference (tx hash / pi_... ID), settlement\n * - Multi-method 402 offers (may be multiple WWW-Authenticate headers)\n *\n * What we do NOT verify here (pass-through):\n * - HMAC challenge binding (requires merchant's MPP_SECRET_KEY)\n * - Payment proof cryptography (Tempo tx sig, Stripe SPT, Lightning preimage)\n * — each requires upstream connectivity\n *\n * Verification (expiry + BodyDigest + source extraction) in mpp-verify.ts.\n */\n\nimport { Challenge, Credential, Receipt } from 'mppx';\n\nexport interface MPPChallengeSummary {\n id: string;\n realm: string;\n method: string;\n intent: string;\n /** Method-specific request data (amount, currency, recipient, etc.) */\n request: Record<string, unknown>;\n expires?: string;\n digest?: string;\n description?: string;\n opaque?: Record<string, string>;\n}\n\nexport interface MPPCredentialSummary {\n challenge: MPPChallengeSummary;\n /** DID or chain-native key identifying the payer. */\n source?: string;\n /** Method-specific payment proof (Tempo tx, SPT, Lightning preimage, etc.). */\n payload: unknown;\n}\n\nexport interface MPPReceiptSummary {\n method?: string;\n reference?: string;\n externalId?: string;\n status?: string;\n timestamp?: string;\n raw: Record<string, unknown>;\n}\n\nexport type MPPKind = 'challenge' | 'credential' | 'receipt' | 'error' | 'unknown';\n\nexport interface MPPRequestContext {\n kind: MPPKind;\n /** For 402 responses: one or more challenge offers. */\n challenges?: MPPChallengeSummary[];\n /** For requests with Authorization: Payment header. */\n credential?: MPPCredentialSummary;\n /** For 200 responses with Payment-Receipt header. */\n receipt?: MPPReceiptSummary;\n /** For problem+json error responses. */\n error?: { type?: string; title?: string; detail?: string };\n /** Detected payment methods offered (for multi-method 402). */\n offeredMethods?: string[];\n /** Raw body captured for BodyDigest verification in mpp-verify.ts. */\n rawBody?: string;\n}\n\nexport interface MPPRequestLike {\n method: string;\n url: string;\n headers: Record<string, string | string[] | undefined>;\n body?: unknown;\n rawBody?: string;\n}\n\nexport interface MPPResponseLike {\n status: number;\n headers: Record<string, string | string[] | undefined>;\n body?: unknown;\n rawBody?: string;\n}\n\n/**\n * Extract MPP context from an agent → merchant request.\n * Looks for `Authorization: Payment <credential>` header.\n */\nexport function extractMPPFromRequest(request: MPPRequestLike): MPPRequestContext | null {\n const auth = readHeader(request.headers, 'authorization');\n if (!auth || !/^\\s*Payment\\s+/i.test(auth)) return null;\n\n try {\n const credential = Credential.deserialize(auth);\n return {\n kind: 'credential',\n credential: {\n challenge: summarizeChallenge(credential.challenge),\n source: credential.source,\n payload: credential.payload,\n },\n rawBody: request.rawBody,\n };\n } catch {\n return { kind: 'error', error: { type: 'invalid-credential-encoding' } };\n }\n}\n\n/**\n * Extract MPP context from a merchant → agent response.\n * Handles 402 (challenge offers), 200 (receipt), 4xx (problem+json errors).\n */\nexport function extractMPPFromResponse(response: MPPResponseLike): MPPRequestContext | null {\n if (response.status === 402) {\n const challenges = collectChallenges(response);\n if (challenges.length === 0) return null;\n const methods = Array.from(new Set(challenges.map((c) => c.method)));\n return {\n kind: 'challenge',\n challenges,\n offeredMethods: methods,\n };\n }\n\n const receiptHeader = readHeader(response.headers, 'payment-receipt');\n if (receiptHeader) {\n try {\n const parsed = Receipt.deserialize(receiptHeader);\n const r = parsed as unknown as Record<string, unknown>;\n return {\n kind: 'receipt',\n receipt: {\n method: coerceString(r.method),\n reference: coerceString(r.reference),\n externalId: coerceString(r.externalId ?? r.external_id),\n status: coerceString(r.status),\n timestamp: coerceString(r.timestamp),\n raw: r,\n },\n };\n } catch {\n return { kind: 'error', error: { type: 'invalid-receipt-encoding' } };\n }\n }\n\n const contentType = readHeader(response.headers, 'content-type');\n if (contentType && /application\\/problem\\+json/i.test(contentType)) {\n const body =\n typeof response.body === 'object' && response.body !== null\n ? (response.body as Record<string, unknown>)\n : {};\n return {\n kind: 'error',\n error: {\n type: coerceString(body.type),\n title: coerceString(body.title),\n detail: coerceString(body.detail),\n },\n };\n }\n\n return null;\n}\n\n/**\n * Extract from either a request OR a response, auto-detecting which has MPP\n * artifacts. Convenience for pipeline callers.\n */\nexport function extractMPPContext(\n message:\n | { request: MPPRequestLike }\n | { response: MPPResponseLike }\n | (MPPRequestLike & Partial<MPPResponseLike>)\n): MPPRequestContext | null {\n if ('request' in message) return extractMPPFromRequest(message.request);\n if ('response' in message) return extractMPPFromResponse(message.response);\n if (typeof (message as MPPResponseLike).status === 'number') {\n return extractMPPFromResponse(message as MPPResponseLike);\n }\n return extractMPPFromRequest(message as MPPRequestLike);\n}\n\nfunction collectChallenges(response: MPPResponseLike): MPPChallengeSummary[] {\n const wwwAuth = readHeader(response.headers, 'www-authenticate');\n if (!wwwAuth) return [];\n const headers = new Headers();\n headers.set('www-authenticate', wwwAuth);\n\n const out: MPPChallengeSummary[] = [];\n try {\n const list = Challenge.fromHeadersList(headers);\n for (const ch of list) {\n out.push(summarizeChallenge(ch as unknown as Challenge.Challenge));\n }\n } catch {\n // fall through with empty list\n }\n return out;\n}\n\nfunction summarizeChallenge(\n ch: Challenge.Challenge | Record<string, unknown>\n): MPPChallengeSummary {\n const raw = ch as Record<string, unknown>;\n return {\n id: coerceString(raw.id) ?? '',\n realm: coerceString(raw.realm) ?? '',\n method: coerceString(raw.method) ?? '',\n intent: coerceString(raw.intent) ?? '',\n request: (raw.request as Record<string, unknown>) ?? {},\n expires: coerceString(raw.expires),\n digest: coerceString(raw.digest),\n description: coerceString(raw.description),\n opaque: raw.opaque as Record<string, string> | undefined,\n };\n}\n\nfunction readHeader(\n headers: Record<string, string | string[] | undefined>,\n name: string\n): string | undefined {\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === name) {\n const raw = headers[key];\n if (typeof raw === 'string') return raw;\n if (Array.isArray(raw)) return raw.join(', ');\n }\n }\n return undefined;\n}\n\nfunction coerceString(v: unknown): string | undefined {\n return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n","/**\n * MPP verification — expiry + optional BodyDigest + source extraction.\n *\n * We do NOT verify the challenge's HMAC binding (needs merchant's secret)\n * or the cryptographic payment proof (per-method, requires upstream\n * connectivity). Those are the merchant's / settlement layer's job.\n *\n * Our job: structural correctness, expiry policy, tamper detection via\n * optional BodyDigest, and identity extraction for PDLSS binding.\n */\n\nimport { BodyDigest } from 'mppx';\nimport type { MPPRequestContext } from './mpp';\nimport { defaultNonceStore, type NonceStore } from './nonce-store';\n\nexport interface MPPVerifyInput {\n context: MPPRequestContext;\n /** Raw request body to validate BodyDigest against, if the challenge declares one. */\n rawBody?: string;\n /** Seconds of clock-skew tolerance on challenge.expires. Default 60 (audit F-A1-05). */\n clockSkewSec?: number;\n /** Injectable for deterministic tests. */\n now?: () => number;\n /** Optional replay-protection store. Defaults to in-process LRU. Audit F-A1-05. */\n nonceStore?: NonceStore;\n}\n\nexport interface MPPVerifyResult {\n ok: boolean;\n expiryOk: boolean;\n bodyDigestOk: boolean | null;\n source?: string;\n method?: string;\n error?: string;\n}\n\nexport function verifyMPP(input: MPPVerifyInput): MPPVerifyResult {\n const { context } = input;\n const tolerance = input.clockSkewSec ?? 60;\n const nowSec = input.now ? input.now() : Math.floor(Date.now() / 1000);\n const nonceStore = input.nonceStore ?? defaultNonceStore;\n\n // Extract the challenge under test — for credential flow, from inside the\n // wrapped challenge; for bare challenge flow, from context.challenges[0].\n const challenge = context.credential?.challenge ?? (context.challenges && context.challenges[0]);\n const source = context.credential?.source;\n const method = challenge?.method;\n\n let expiryOk = true;\n if (challenge?.expires) {\n const parsedExpiry = Date.parse(challenge.expires);\n if (!Number.isFinite(parsedExpiry)) {\n return {\n ok: false,\n expiryOk: false,\n bodyDigestOk: null,\n source,\n method,\n error: 'unparseable challenge.expires',\n };\n }\n const expiresSec = Math.floor(parsedExpiry / 1000);\n if (nowSec > expiresSec + tolerance) {\n expiryOk = false;\n }\n }\n\n // Audit F-A1-04: previously when rawBody was present but the challenge\n // omitted `digest`, bodyDigestOk stayed `null` and was treated as OK.\n // Attacker could strip the digest field from the challenge and tamper\n // with body. Now: when rawBody is present, digest is REQUIRED.\n let bodyDigestOk: boolean | null = null;\n if (input.rawBody !== undefined) {\n if (!challenge?.digest) {\n bodyDigestOk = false;\n } else {\n try {\n if (!/^sha-256=/.test(challenge.digest)) {\n bodyDigestOk = false;\n } else {\n bodyDigestOk = BodyDigest.verify(challenge.digest as `sha-256=${string}`, input.rawBody);\n }\n } catch {\n bodyDigestOk = false;\n }\n }\n }\n\n // Audit F-A1-05: replay protection. Use challenge nonce + digest as\n // replay key when available; fall back to digest alone. Window matches\n // the expiry tolerance.\n let replayOk = true;\n if (challenge?.digest && expiryOk) {\n const replayKey = `mpp:${challenge.digest}:${(challenge as { nonce?: string }).nonce ?? ''}`;\n const expiresAt = (nowSec + tolerance) * 1000;\n if (nonceStore.seen(replayKey, expiresAt)) {\n replayOk = false;\n }\n }\n\n const ok = expiryOk && (bodyDigestOk === null || bodyDigestOk === true) && replayOk;\n const errors: string[] = [];\n if (!expiryOk) errors.push('challenge expired');\n if (bodyDigestOk === false) {\n errors.push(\n input.rawBody !== undefined && !challenge?.digest\n ? 'body digest required when rawBody present'\n : 'body digest mismatch'\n );\n }\n if (!replayOk) errors.push('MPP challenge replay — already seen within tolerance window');\n\n return {\n ok,\n expiryOk,\n bodyDigestOk,\n source,\n method,\n error: errors.length > 0 ? errors.join('; ') : undefined,\n };\n}\n","/**\n * x402 (Coinbase / Linux Foundation x402 Foundation) extractor.\n *\n * Wraps @x402/core's schema parsers. x402 Foundation launched April 2 2026\n * with v2 adding network-agnostic identifiers + multiple facilitators +\n * Bazaar discovery. MPP (Machine Payments Protocol) is the IETF-formalised\n * superset of x402; this module normalizes x402 output to MPP-shape so\n * downstream pipeline code is uniform.\n *\n * Where x402 lives on the wire:\n * - 402 response body (v2) OR `X-PAYMENT-REQUIRED` header (v1) — PaymentRequired\n * - Request body (v2) OR `X-PAYMENT` header (v1, base64) — PaymentPayload\n */\n\nimport {\n validatePaymentRequired,\n validatePaymentPayload,\n type PaymentRequired,\n type PaymentPayload,\n type PaymentRequirements,\n} from '@x402/core/schemas';\nimport { safeBase64Decode } from '@x402/core/utils';\n\nexport type X402Kind = 'required' | 'payload' | 'error' | 'unknown';\n\nexport interface X402RequirementsSummary {\n scheme: string;\n network: string;\n asset: string;\n /** Normalized to string for v1/v2 compat — v1 uses maxAmountRequired, v2 uses amount. */\n amount: string;\n payTo: string;\n maxTimeoutSeconds?: number;\n resource?: string;\n description?: string;\n}\n\nexport interface X402RequestContext {\n kind: X402Kind;\n version: 1 | 2 | null;\n /** For 402 responses: the PaymentRequired body. */\n paymentRequired?: {\n resource: string;\n accepts: X402RequirementsSummary[];\n extensions?: Record<string, unknown>;\n error?: string;\n };\n /** For request body (v2) or X-PAYMENT header (v1 base64): the PaymentPayload. */\n paymentPayload?: {\n scheme: string;\n network: string;\n /** Free-form per-scheme payload (e.g. EIP-3009 authorization, Solana tx). */\n payload: Record<string, unknown>;\n extensions?: Record<string, unknown>;\n };\n error?: { type: string; detail?: string };\n /** Whether this was parsed from a header (v1 back-compat) or body (v2). */\n source: 'header' | 'body' | null;\n}\n\nexport interface X402RequestLike {\n method?: string;\n url?: string;\n headers?: Record<string, string | string[] | undefined>;\n body?: unknown;\n}\n\nexport interface X402ResponseLike {\n status?: number;\n headers?: Record<string, string | string[] | undefined>;\n body?: unknown;\n}\n\n/**\n * Extract x402 PaymentPayload from an agent → merchant request.\n * Checks v2 body (if it parses as PaymentPayload) and v1 X-PAYMENT header.\n */\nexport function extractX402FromRequest(request: X402RequestLike): X402RequestContext | null {\n const headerValue = readHeader(request.headers, 'x-payment');\n\n // v2 body path first\n if (request.body && typeof request.body === 'object') {\n const parsed = tryParsePayload(request.body);\n if (parsed) return buildPayloadContext(parsed, 'body');\n }\n\n // v1 header path\n if (headerValue) {\n try {\n const decoded = safeBase64Decode(headerValue);\n if (decoded) {\n const json = JSON.parse(decoded);\n const parsed = tryParsePayload(json);\n if (parsed) return buildPayloadContext(parsed, 'header');\n }\n } catch {\n return {\n kind: 'error',\n version: 1,\n source: 'header',\n error: { type: 'invalid-x402-payload' },\n };\n }\n }\n\n return null;\n}\n\n/**\n * Extract x402 PaymentRequired from a merchant → agent 402 response.\n */\nexport function extractX402FromResponse(response: X402ResponseLike): X402RequestContext | null {\n if (response.status !== 402) return null;\n\n // v2 body path\n if (response.body && typeof response.body === 'object') {\n const parsed = tryParseRequired(response.body);\n if (parsed) return buildRequiredContext(parsed, 'body');\n }\n\n // v1 header path\n const headerValue = readHeader(response.headers, 'x-payment-required');\n if (headerValue) {\n try {\n const decoded = safeBase64Decode(headerValue);\n if (decoded) {\n const json = JSON.parse(decoded);\n const parsed = tryParseRequired(json);\n if (parsed) return buildRequiredContext(parsed, 'header');\n }\n } catch {\n return {\n kind: 'error',\n version: 1,\n source: 'header',\n error: { type: 'invalid-x402-required' },\n };\n }\n }\n\n return null;\n}\n\nexport function extractX402Context(\n message:\n | { request: X402RequestLike }\n | { response: X402ResponseLike }\n | (X402RequestLike & Partial<X402ResponseLike>)\n): X402RequestContext | null {\n if ('request' in message) return extractX402FromRequest(message.request);\n if ('response' in message) return extractX402FromResponse(message.response);\n if (typeof (message as X402ResponseLike).status === 'number') {\n return extractX402FromResponse(message as X402ResponseLike);\n }\n return extractX402FromRequest(message as X402RequestLike);\n}\n\nfunction tryParseRequired(data: unknown): PaymentRequired | null {\n try {\n return validatePaymentRequired(data);\n } catch {\n return null;\n }\n}\n\nfunction tryParsePayload(data: unknown): PaymentPayload | null {\n try {\n return validatePaymentPayload(data);\n } catch {\n return null;\n }\n}\n\nfunction buildRequiredContext(\n parsed: PaymentRequired,\n source: 'header' | 'body'\n): X402RequestContext {\n const asRecord = parsed as unknown as Record<string, unknown>;\n const version = coerceVersion(asRecord.x402Version);\n const accepts = (asRecord.accepts as PaymentRequirements[] | undefined) ?? [];\n return {\n kind: 'required',\n version,\n source,\n paymentRequired: {\n resource: resolveResource(asRecord.resource),\n accepts: accepts.map(summarizeRequirement),\n extensions: asRecord.extensions as Record<string, unknown> | undefined,\n error: typeof asRecord.error === 'string' ? asRecord.error : undefined,\n },\n };\n}\n\nfunction buildPayloadContext(\n parsed: PaymentPayload,\n source: 'header' | 'body'\n): X402RequestContext {\n const asRecord = parsed as unknown as Record<string, unknown>;\n const version = coerceVersion(asRecord.x402Version);\n const accepted = asRecord.accepted as PaymentRequirements | undefined;\n const payload = (asRecord.payload as Record<string, unknown>) ?? {};\n return {\n kind: 'payload',\n version,\n source,\n paymentPayload: {\n scheme: accepted?.scheme ?? (typeof asRecord.scheme === 'string' ? asRecord.scheme : ''),\n network: accepted?.network ?? (typeof asRecord.network === 'string' ? asRecord.network : ''),\n payload,\n extensions: asRecord.extensions as Record<string, unknown> | undefined,\n },\n };\n}\n\nfunction summarizeRequirement(req: PaymentRequirements): X402RequirementsSummary {\n const r = req as unknown as Record<string, unknown>;\n const amount = (r.amount ?? r.maxAmountRequired ?? '0') as string;\n return {\n scheme: (r.scheme as string) ?? '',\n network: (r.network as string) ?? '',\n asset: (r.asset as string) ?? '',\n amount: String(amount),\n payTo: (r.payTo as string) ?? '',\n maxTimeoutSeconds: typeof r.maxTimeoutSeconds === 'number' ? r.maxTimeoutSeconds : undefined,\n resource: typeof r.resource === 'string' ? r.resource : undefined,\n description: typeof r.description === 'string' ? r.description : undefined,\n };\n}\n\nfunction resolveResource(v: unknown): string {\n if (typeof v === 'string') return v;\n if (v && typeof v === 'object' && 'url' in v && typeof (v as { url: unknown }).url === 'string') {\n return (v as { url: string }).url;\n }\n return '';\n}\n\nfunction coerceVersion(v: unknown): 1 | 2 | null {\n if (v === 1 || v === 2) return v;\n return null;\n}\n\nfunction readHeader(\n headers: Record<string, string | string[] | undefined> | undefined,\n name: string\n): string | undefined {\n if (!headers) return undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === name) {\n const raw = headers[key];\n if (typeof raw === 'string') return raw;\n if (Array.isArray(raw)) return raw[0];\n }\n }\n return undefined;\n}\n","/**\n * VI (Verifiable Intent) 3-layer SD-JWT chain verification.\n *\n * VI chains: L1 (credential provider → wallet) → L2 (user → agent) → L3\n * (agent → merchant). L3 itself can split into L3a (payment mandate) + L3b\n * (checkout mandate) cross-referenced via transaction_id, with L3b carrying\n * a checkout_hash (VI constraint type 8) that must match SHA-256 of the L2\n * checkout disclosure.\n *\n * Signature primitives are delegated to @sd-jwt/core (via our extractor);\n * cnf.jwk chain-walking + cross-references + checkout_hash binding is\n * AstraSync-specific composition logic — that's the whitespace here.\n *\n * This module does NOT re-verify selective-disclosure hashes (the extractor\n * already applied them via @sd-jwt/decode). It DOES verify:\n * - cnf.jwk in L1 payload points to L2's signing key (thumbprint match)\n * - cnf.jwk in L2 payload points to L3's signing key\n * - L3a.transaction_id === L3b.transaction_id (when both present)\n * - L3b.checkout_hash === SHA-256(L2 canonical checkout disclosure) — type 8\n * - mandate-level `exp` is not in the past (beyond clock skew)\n *\n * Cryptographic signature verification on each layer uses the verifier\n * callback the caller supplies (e.g. resolves via @sd-jwt/core with the\n * right JWK from the L1 issuer's JWKS).\n */\n\nimport { createHash, webcrypto } from 'node:crypto';\nimport type { JWK } from 'jose';\nimport { defaultNonceStore, type NonceStore } from './nonce-store';\n\nexport interface VILayer {\n /** Compact SD-JWT / JWS for this layer. */\n compact: string;\n /** Decoded JWT payload (already disclosure-merged). */\n payload: Record<string, unknown>;\n /** Decoded JWT header. */\n header: Record<string, unknown>;\n}\n\nexport interface VIVerifyInput {\n /**\n * Layers in chain order. L1 is REQUIRED by default (audit F-A1-02) —\n * without L1 there is no chain root and L2 can be verified against any\n * attacker-supplied key. Callers who have resolved L2's signing key by\n * a trusted out-of-band mechanism (wallet binding, prior protocol step)\n * MUST set `allowUnboundChain: true` AND supply `expectedL2Key`.\n */\n layers: {\n l1?: VILayer;\n l2: VILayer;\n l3a?: VILayer;\n l3b?: VILayer;\n };\n /**\n * Verifier callback invoked per layer. Should return true iff the layer's\n * JWS signature verifies against the resolved public key (for L2 this is\n * L1's cnf.jwk; for L3 this is L2's cnf.jwk; for L1 this is the issuer's\n * JWKS per `iss` claim).\n */\n verifySignature: (layer: VILayer, expectedKey: JWK | null) => Promise<boolean>;\n /**\n * Clock skew tolerance in seconds for expiry checks. Default 60s (audit\n * F-A1-05 tightening from the previous 300s default).\n */\n clockSkewSec?: number;\n now?: () => number;\n /**\n * Explicit opt-in to verify a chain with L1 omitted. Audit F-A1-02 fix\n * — defaults to false. When true, `expectedL2Key` MUST also be supplied\n * (used as the expected signing key for L2 verification).\n */\n allowUnboundChain?: boolean;\n /** Required when allowUnboundChain === true. */\n expectedL2Key?: JWK;\n /** Optional replay-protection store. Defaults to in-process LRU. */\n nonceStore?: NonceStore;\n}\n\nexport interface VIVerifyResult {\n ok: boolean;\n checks: {\n l1SigOk: boolean | null;\n l2SigOk: boolean;\n l3aSigOk: boolean | null;\n l3bSigOk: boolean | null;\n l1BindsL2: boolean;\n l2BindsL3: boolean;\n l3aL3bTxnIdMatch: boolean | null;\n checkoutHashOk: boolean | null;\n expiryOk: boolean;\n };\n errors: string[];\n}\n\nexport async function verifyVIChain(input: VIVerifyInput): Promise<VIVerifyResult> {\n const errors: string[] = [];\n const tolerance = input.clockSkewSec ?? 60;\n const now = input.now ? input.now() : Math.floor(Date.now() / 1000);\n const { l1, l2, l3a, l3b } = input.layers;\n const nonceStore = input.nonceStore ?? defaultNonceStore;\n\n // Audit F-A1-02: L1 is required unless caller explicitly opts into an\n // unbound chain AND supplies the expected L2 signing key. Otherwise L2\n // would be verified against null, which most verifier callbacks\n // interpret as \"use the JWK in the JWS header\" — accepting any\n // attacker-supplied key.\n if (!l1) {\n if (!input.allowUnboundChain) {\n errors.push(\n 'L1 missing — chain root unbound (set allowUnboundChain + expectedL2Key to override)'\n );\n } else if (!input.expectedL2Key) {\n errors.push('allowUnboundChain set but expectedL2Key missing');\n }\n }\n\n // Signature verification ---------------------------------\n const l1SigOk = l1 ? await input.verifySignature(l1, null) : null;\n if (l1 && !l1SigOk) errors.push('L1 signature invalid');\n\n const l1Cnf = extractCnfJwk(l1?.payload);\n // Use L1's cnf.jwk when L1 present; otherwise (opt-in unbound chain) use\n // the caller-supplied expectedL2Key. Never null — F-A1-02.\n const l2ExpectedKey: JWK | null = l1Cnf ?? input.expectedL2Key ?? null;\n const l2SigOk = await input.verifySignature(l2, l2ExpectedKey);\n if (!l2SigOk) errors.push('L2 signature invalid');\n\n // Audit F-A1-05: replay protection on L2 signature. Use the compact JWS\n // as the nonce key; the signature is unique per request. Window matches\n // tolerance (denied replays past the window are already rejected by\n // expiry check, so we don't need to retain entries longer).\n if (l2SigOk) {\n const replayKey = `vi:l2:${l2.compact}`;\n const expiresAt = now * 1000 + tolerance * 1000;\n if (nonceStore.seen(replayKey, expiresAt)) {\n errors.push('L2 signature replay — already seen within tolerance window');\n }\n }\n\n const l2Cnf = extractCnfJwk(l2.payload);\n const l3aSigOk = l3a ? await input.verifySignature(l3a, l2Cnf ?? null) : null;\n if (l3a && !l3aSigOk) errors.push('L3a signature invalid');\n const l3bSigOk = l3b ? await input.verifySignature(l3b, l2Cnf ?? null) : null;\n if (l3b && !l3bSigOk) errors.push('L3b signature invalid');\n\n // cnf.jwk binding ----------------------------------------\n let l1BindsL2 = true;\n if (l1Cnf) {\n const l2KeyFromHeader = await jwkForLayer(l2);\n l1BindsL2 = l2KeyFromHeader ? await thumbprintsMatch(l1Cnf, l2KeyFromHeader) : false;\n if (!l1BindsL2) errors.push('L1.cnf.jwk does not bind L2 signing key');\n }\n\n let l2BindsL3 = true;\n if (l2Cnf && (l3a || l3b)) {\n const l3Layer = l3a ?? l3b!;\n const l3KeyFromHeader = await jwkForLayer(l3Layer);\n l2BindsL3 = l3KeyFromHeader ? await thumbprintsMatch(l2Cnf, l3KeyFromHeader) : false;\n if (!l2BindsL3) errors.push('L2.cnf.jwk does not bind L3 signing key');\n }\n\n // L3a/L3b cross-reference --------------------------------\n let l3aL3bTxnIdMatch: boolean | null = null;\n if (l3a && l3b) {\n const a = coerceString(l3a.payload.transaction_id ?? l3a.payload.transactionId);\n const b = coerceString(l3b.payload.transaction_id ?? l3b.payload.transactionId);\n if (a && b) {\n l3aL3bTxnIdMatch = a === b;\n if (!l3aL3bTxnIdMatch) {\n errors.push(`L3a.transaction_id (${a}) does not match L3b.transaction_id (${b})`);\n }\n }\n }\n\n // checkout_hash (VI constraint type 8) -------------------\n let checkoutHashOk: boolean | null = null;\n if (l3b) {\n const declaredHash = coerceString(\n l3b.payload.checkout_hash ??\n l3b.payload.conditional_transaction_id ??\n (l3b.payload.payment_reference as Record<string, unknown> | undefined)?.checkout_hash\n );\n if (declaredHash) {\n const computed = computeCheckoutHashFromL2(l2);\n checkoutHashOk = computed ? declaredHash === computed : false;\n if (!checkoutHashOk) {\n errors.push('L3b.checkout_hash does not match SHA-256 of L2 checkout disclosure');\n }\n }\n }\n\n // Expiry policy ------------------------------------------\n const expiryOk = checkExpiryAcross([l1, l2, l3a, l3b], tolerance, now, errors);\n\n // Audit F-A1-02 + F-A1-05: ok also requires the chain-root + replay\n // checks above to have passed (errors array captures both). Previously\n // `ok` was derived purely from individual check booleans, so the L1-\n // required errors and replay-detected errors didn't gate it.\n const noUnboundChainOrReplayErrors = !errors.some(\n (e) =>\n e.startsWith('L1 missing') ||\n e.startsWith('allowUnboundChain set') ||\n e.startsWith('L2 signature replay')\n );\n\n const ok =\n l1SigOk !== false &&\n l2SigOk &&\n l3aSigOk !== false &&\n l3bSigOk !== false &&\n l1BindsL2 &&\n l2BindsL3 &&\n l3aL3bTxnIdMatch !== false &&\n checkoutHashOk !== false &&\n expiryOk &&\n noUnboundChainOrReplayErrors;\n\n return {\n ok,\n checks: {\n l1SigOk,\n l2SigOk,\n l3aSigOk,\n l3bSigOk,\n l1BindsL2,\n l2BindsL3,\n l3aL3bTxnIdMatch,\n checkoutHashOk,\n expiryOk,\n },\n errors,\n };\n}\n\nfunction extractCnfJwk(payload: Record<string, unknown> | undefined): JWK | null {\n if (!payload) return null;\n const cnf = payload.cnf as Record<string, unknown> | undefined;\n if (!cnf) return null;\n const jwk = cnf.jwk as JWK | undefined;\n return jwk ?? null;\n}\n\nasync function jwkForLayer(layer: VILayer): Promise<JWK | null> {\n // Prefer explicit cnf.jwk in the header, then payload; fallback to null.\n const fromHeader = extractCnfJwk(layer.header);\n if (fromHeader) return fromHeader;\n const fromPayload = extractCnfJwk(layer.payload);\n return fromPayload;\n}\n\nasync function thumbprintsMatch(a: JWK, b: JWK): Promise<boolean> {\n try {\n const ta = await jwkThumbprint(a);\n const tb = await jwkThumbprint(b);\n return ta === tb;\n } catch {\n return false;\n }\n}\n\n// RFC 7638 thumbprint: SHA-256 over the canonical JSON of required JWK members.\nasync function jwkThumbprint(jwk: JWK): Promise<string> {\n const canonical = canonicalJwk(jwk);\n const bytes = new TextEncoder().encode(JSON.stringify(canonical));\n const subtle = webcrypto.subtle as SubtleCrypto;\n const buffer = await new Promise<ArrayBuffer>((resolve, reject) => {\n const source = new ArrayBuffer(bytes.byteLength);\n new Uint8Array(source).set(bytes);\n subtle.digest('SHA-256', source).then(resolve).catch(reject);\n });\n return Buffer.from(new Uint8Array(buffer)).toString('base64url').replace(/=+$/, '');\n}\n\nfunction canonicalJwk(jwk: JWK): Record<string, string> {\n // Per RFC 7638: members must appear in lexicographic order; only required\n // fields per kty are included.\n if (jwk.kty === 'EC') {\n return { crv: jwk.crv ?? '', kty: 'EC', x: jwk.x ?? '', y: jwk.y ?? '' };\n }\n if (jwk.kty === 'OKP') {\n return { crv: jwk.crv ?? '', kty: 'OKP', x: jwk.x ?? '' };\n }\n if (jwk.kty === 'RSA') {\n return { e: jwk.e ?? '', kty: 'RSA', n: jwk.n ?? '' };\n }\n return { kty: jwk.kty ?? '' };\n}\n\nfunction computeCheckoutHashFromL2(l2: VILayer): string | null {\n const checkoutDisclosure = (l2.payload.checkout ?? l2.payload.checkout_mandate) as unknown;\n if (!checkoutDisclosure) return null;\n const canonical = canonicalStringify(checkoutDisclosure);\n const hash = createHash('sha256').update(canonical).digest('base64url').replace(/=+$/, '');\n return hash;\n}\n\nfunction canonicalStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) return '[' + value.map(canonicalStringify).join(',') + ']';\n const entries = Object.entries(value as Record<string, unknown>).sort(([a], [b]) =>\n a < b ? -1 : a > b ? 1 : 0\n );\n return (\n '{' + entries.map(([k, v]) => JSON.stringify(k) + ':' + canonicalStringify(v)).join(',') + '}'\n );\n}\n\nfunction checkExpiryAcross(\n layers: Array<VILayer | undefined>,\n toleranceSec: number,\n nowSec: number,\n errors: string[]\n): boolean {\n let ok = true;\n const names = ['L1', 'L2', 'L3a', 'L3b'];\n layers.forEach((layer, idx) => {\n if (!layer) return;\n const exp = toUnixSeconds(layer.payload.exp ?? layer.payload.expires);\n if (exp === undefined) return;\n if (nowSec > exp + toleranceSec) {\n errors.push(`${names[idx]} mandate expired at ${exp}`);\n ok = false;\n }\n });\n return ok;\n}\n\nfunction toUnixSeconds(v: unknown): number | undefined {\n if (typeof v === 'number' && Number.isFinite(v)) return v;\n if (typeof v === 'string') {\n const asInt = Number(v);\n if (Number.isFinite(asInt) && asInt > 0) {\n return asInt >= 1e12 ? Math.floor(asInt / 1000) : Math.floor(asInt);\n }\n const parsed = Date.parse(v);\n if (Number.isFinite(parsed)) return Math.floor(parsed / 1000);\n }\n return undefined;\n}\n\nfunction coerceString(v: unknown): string | undefined {\n return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n","/**\n * Visa JWKS registry resolver.\n *\n * Default endpoint: https://mcp.visa.com/.well-known/jwks (per Visa TAP spec).\n * Wraps jose.createRemoteJWKSet which handles caching + rotation natively.\n */\n\nimport { createRemoteJWKSet, type JWK } from 'jose';\nimport type { RegistryResolver, ResolveContext } from './types';\n\nconst DEFAULT_VISA_JWKS_URL = 'https://mcp.visa.com/.well-known/jwks';\n\nexport interface VisaRegistryOptions {\n jwksUrl?: string;\n cacheMaxAge?: number;\n cooldownDuration?: number;\n}\n\nexport function createVisaRegistry(options: VisaRegistryOptions = {}): RegistryResolver {\n const url = new URL(options.jwksUrl ?? DEFAULT_VISA_JWKS_URL);\n const jwks = createRemoteJWKSet(url, {\n cacheMaxAge: options.cacheMaxAge,\n cooldownDuration: options.cooldownDuration,\n });\n\n return {\n name: 'visa',\n async resolve(kid: string, context?: ResolveContext): Promise<JWK | null> {\n if (!kid) return null;\n try {\n const key = await jwks({\n kid,\n alg: context?.algorithm ?? 'ES256',\n typ: 'JWT',\n });\n return exportJwkFromKeyLike(key);\n } catch {\n return null;\n }\n },\n };\n}\n\nasync function exportJwkFromKeyLike(keyLike: unknown): Promise<JWK | null> {\n if (!keyLike) return null;\n // jose returns KeyObject or CryptoKey — both export via exportJWK at caller side.\n // Runtime shape check: if it already looks like a JWK, pass through.\n if (typeof keyLike === 'object' && 'kty' in (keyLike as object)) {\n return keyLike as JWK;\n }\n const { exportJWK } = await import('jose');\n try {\n return await exportJWK(keyLike as Parameters<typeof exportJWK>[0]);\n } catch {\n return null;\n }\n}\n","/**\n * Cross-Protocol Transport Module\n *\n * Provides adapters for injecting/extracting AstraSync credentials\n * across HTTP, A2A, and MCP protocols.\n */\n\nimport type { AstraSyncCredentials, ProtocolTransport } from '../types';\nimport { setHttpHeaders, extractHttpCredentials } from './http';\nimport { setA2AMetadata, extractA2ACredentials } from './a2a';\nimport { setMcpMeta, extractMcpCredentials } from './mcp';\n\nexport { setHttpHeaders, extractHttpCredentials } from './http';\nexport { setA2AMetadata, extractA2ACredentials } from './a2a';\nexport { setMcpMeta, extractMcpCredentials } from './mcp';\n\n// Commerce protocol extractors + verifiers (PR 4+5)\nexport * from './purpose-mapping';\nexport * from './transaction-value';\nexport * from './rfc9421';\nexport * from './rfc9421-verify';\nexport * from './ucp';\nexport * from './acp';\nexport * from './vi';\nexport * from './stripe-webhook';\nexport * from './constraint-eval';\nexport * from './identity-binding';\nexport * from './ap2';\nexport * from './ap2-verify';\nexport * from './acp-verify';\nexport * from './mpp';\nexport * from './mpp-verify';\nexport * from './x402';\nexport * from './vi-verify';\nexport * from './commerce-pipeline';\nexport * from './extractor-registry';\nexport * from './registry/types';\nexport { createVisaRegistry } from './registry/visa';\nexport { createMastercardRegistry } from './registry/mastercard';\nexport { createWebBotAuthRegistry } from './registry/web-bot-auth';\n\n/**\n * Auto-detect protocol from request/context shape.\n */\nexport function detectProtocol(context: Record<string, unknown>): ProtocolTransport {\n // A2A: has metadata block with task-like structure\n if (context.metadata && typeof context.metadata === 'object') {\n return 'a2a';\n }\n\n // MCP: has _meta block (MCP convention)\n if (context._meta && typeof context._meta === 'object') {\n return 'mcp';\n }\n\n // Default to HTTP\n return 'http';\n}\n\n/**\n * Apply credentials to any protocol target.\n */\nexport function applyCredentials(\n protocol: ProtocolTransport,\n target: Record<string, unknown>,\n credentials: AstraSyncCredentials\n): Record<string, unknown> {\n switch (protocol) {\n case 'http':\n return setHttpHeaders(target as Record<string, string>, credentials);\n case 'a2a':\n return setA2AMetadata(target, credentials);\n case 'mcp':\n return setMcpMeta(target, credentials);\n default:\n return target;\n }\n}\n\n/**\n * Extract credentials from any protocol context.\n */\nexport function extractCredentialsFromProtocol(\n protocol: ProtocolTransport,\n context: Record<string, unknown>\n): AstraSyncCredentials | null {\n switch (protocol) {\n case 'http':\n return extractHttpCredentials(context as Record<string, string | string[] | undefined>);\n case 'a2a':\n return extractA2ACredentials(context);\n case 'mcp':\n return extractMcpCredentials(context);\n default:\n return null;\n }\n}\n","/**\n * Agent-side SDK errors.\n */\n\nexport class AstraSyncSdkError extends Error {\n readonly code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'AstraSyncSdkError';\n this.code = code;\n }\n}\n\n/**\n * Thrown when the API key used to initialise AgentClient is not owned by\n * the same account that registered the configured ASTRA-id. Blocks the\n * client from doing anything under a mismatched identity.\n */\nexport class OwnershipMismatchError extends AstraSyncSdkError {\n readonly astraId: string;\n\n constructor(astraId: string) {\n super(\n 'ownership_mismatch',\n `The configured API key does not own agent ${astraId}. Refusing to initialise.`\n );\n this.name = 'OwnershipMismatchError';\n this.astraId = astraId;\n }\n}\n","/**\n * AgentClient — Credential Presentation\n *\n * Agent-side SDK for automatically injecting AstraSync credentials\n * into outgoing requests across all supported protocols.\n */\n\nimport type { AstraSyncCredentials, ProtocolTransport } from '../types';\nimport { setHttpHeaders } from '../transport/http';\nimport { setA2AMetadata } from '../transport/a2a';\nimport { setMcpMeta } from '../transport/mcp';\nimport { applyCredentials } from '../transport';\nimport { OwnershipMismatchError } from './errors';\n\ninterface AgentClientConfig {\n agentId: string;\n verifyUrl?: string;\n challengeUrl?: string;\n pdlss?: AstraSyncCredentials['pdlss'];\n /** Base URL for AstraSync API (used for ownership check). Defaults to https://astrasync.ai/api */\n apiBaseUrl?: string;\n /** API key used to authenticate ownership check + other authenticated calls. */\n apiKey?: string;\n}\n\ninterface FetchOptions extends RequestInit {\n purpose?: string;\n action?: string;\n}\n\nexport class AgentClient {\n private credentials: AstraSyncCredentials;\n private apiBaseUrl: string;\n private apiKey: string | undefined;\n\n constructor(config: AgentClientConfig) {\n this.credentials = {\n agentId: config.agentId,\n verifyUrl: config.verifyUrl ?? 'https://astrasync.ai/api/agents/verify-access',\n challengeUrl: config.challengeUrl,\n pdlss: config.pdlss,\n };\n this.apiBaseUrl = config.apiBaseUrl ?? 'https://astrasync.ai/api';\n this.apiKey = config.apiKey;\n }\n\n /**\n * Async factory that validates the API key's account owns the configured\n * ASTRA-id before returning a usable client. Refuses to initialise on\n * mismatch so a stolen ASTRA-id cannot be paired with a valid (different)\n * API key.\n *\n * Set env `ASTRASYNC_SKIP_OWNERSHIP_CHECK=true` to bypass — intended for\n * test environments only.\n */\n static async create(config: AgentClientConfig): Promise<AgentClient> {\n const client = new AgentClient(config);\n\n const skip =\n typeof process !== 'undefined' && process.env?.ASTRASYNC_SKIP_OWNERSHIP_CHECK === 'true';\n if (skip) return client;\n\n if (!config.apiKey) {\n // Without an API key we cannot bind ownership. Surface this as the\n // same refusal so misconfigured clients fail loudly.\n throw new OwnershipMismatchError(config.agentId);\n }\n\n const owned = await client.verifyOwnership();\n if (!owned) throw new OwnershipMismatchError(config.agentId);\n return client;\n }\n\n /**\n * Calls GET /api/agents/:astraId/ownership with the configured API key.\n * Returns true only when the backend confirms this API key's account\n * owns the configured agent.\n */\n private async verifyOwnership(): Promise<boolean> {\n if (!this.apiKey) return false;\n const url = `${this.apiBaseUrl.replace(/\\/+$/, '')}/api/agents/${encodeURIComponent(\n this.credentials.agentId\n )}/ownership`;\n const resp = await fetch(url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n });\n if (!resp.ok) return false;\n const body = (await resp.json().catch(() => null)) as {\n success?: boolean;\n data?: { owned?: boolean };\n } | null;\n return Boolean(body?.data?.owned);\n }\n\n /**\n * Make an HTTP request with AstraSync headers automatically injected.\n */\n async fetch(url: string, options?: FetchOptions): Promise<Response> {\n const { purpose, action, ...fetchOptions } = options ?? {};\n\n // Build credentials with optional overrides\n const creds: AstraSyncCredentials = { ...this.credentials };\n if (purpose) {\n creds.pdlss = {\n ...creds.pdlss,\n purpose: { category: purpose, action },\n };\n }\n\n // Inject AstraSync headers\n const existingHeaders: Record<string, string> = {};\n if (fetchOptions.headers) {\n if (fetchOptions.headers instanceof Headers) {\n fetchOptions.headers.forEach((value, key) => {\n existingHeaders[key] = value;\n });\n } else if (Array.isArray(fetchOptions.headers)) {\n for (const [key, value] of fetchOptions.headers) {\n existingHeaders[key] = value;\n }\n } else {\n Object.assign(existingHeaders, fetchOptions.headers);\n }\n }\n\n const enrichedHeaders = setHttpHeaders(existingHeaders, creds);\n\n return fetch(url, {\n ...fetchOptions,\n headers: enrichedHeaders,\n });\n }\n\n /**\n * Prepare A2A task metadata with AstraSync credentials.\n */\n prepareA2AMetadata(\n task: Record<string, unknown>,\n overrides?: { purpose?: string; action?: string }\n ): Record<string, unknown> {\n const creds = this.buildCredentials(overrides);\n return setA2AMetadata(task, creds);\n }\n\n /**\n * Prepare MCP params with AstraSync _meta.\n */\n prepareMcpMeta(\n params: Record<string, unknown>,\n overrides?: { purpose?: string; action?: string }\n ): Record<string, unknown> {\n const creds = this.buildCredentials(overrides);\n return setMcpMeta(params, creds);\n }\n\n /**\n * Generic: apply credentials to any protocol.\n */\n applyCredentials(\n protocol: ProtocolTransport,\n target: Record<string, unknown>,\n overrides?: { purpose?: string; action?: string }\n ): Record<string, unknown> {\n const creds = this.buildCredentials(overrides);\n return applyCredentials(protocol, target, creds);\n }\n\n private buildCredentials(overrides?: {\n purpose?: string;\n action?: string;\n }): AstraSyncCredentials {\n if (!overrides?.purpose) return this.credentials;\n\n return {\n ...this.credentials,\n pdlss: {\n ...this.credentials.pdlss,\n purpose: { category: overrides.purpose, action: overrides.action },\n },\n };\n }\n}\n","/**\n * ChallengeHandler — Agent-Side Runtime Challenge Responder\n *\n * Handles incoming runtime challenges from AstraSync's verification service.\n * Agents register pending counterparties before initiating contact,\n * then this handler validates and responds to challenges.\n */\n\ninterface ChallengePayload {\n challengeId: string;\n type: string;\n counterpartyId?: string | null;\n counterpartyUrl?: string | null;\n question?: string;\n issuedAt: string;\n expiresAt: string;\n}\n\ninterface ChallengeResponse {\n status: number;\n body: {\n challengeId: string;\n acknowledged: boolean;\n pendingCounterparties: string[];\n respondedAt: string;\n error?: string;\n };\n}\n\ninterface ChallengeHandlerConfig {\n agentId: string;\n}\n\nexport class ChallengeHandler {\n private agentId: string;\n private pendingCounterparties: Set<string> = new Set();\n\n constructor(config: ChallengeHandlerConfig) {\n this.agentId = config.agentId;\n }\n\n /**\n * Register a counterparty as pending (before initiating contact).\n */\n registerPending(counterpartyId: string): void {\n this.pendingCounterparties.add(counterpartyId);\n }\n\n /**\n * Remove a counterparty from pending list (after interaction complete).\n */\n removePending(counterpartyId: string): void {\n this.pendingCounterparties.delete(counterpartyId);\n }\n\n /**\n * Get current pending counterparties list.\n */\n getPendingList(): string[] {\n return [...this.pendingCounterparties];\n }\n\n /**\n * Express middleware for the challenge endpoint.\n * Mount at: app.post('/astrasync/challenge', handler.expressMiddleware())\n */\n expressMiddleware(): (req: { body: unknown }, res: { status: (code: number) => { json: (body: unknown) => void } }) => void {\n return (req, res) => {\n const result = this.handleChallenge(req.body);\n res.status(result.status).json(result.body);\n };\n }\n\n /**\n * Generic handler (framework-agnostic).\n * Returns { status, body } for the caller to send.\n */\n handleChallenge(body: unknown): ChallengeResponse {\n // Validate payload shape\n if (!body || typeof body !== 'object') {\n return {\n status: 400,\n body: {\n challengeId: '',\n acknowledged: false,\n pendingCounterparties: [],\n respondedAt: new Date().toISOString(),\n error: 'Invalid challenge payload',\n },\n };\n }\n\n const payload = body as ChallengePayload;\n\n if (!payload.challengeId || !payload.issuedAt || !payload.expiresAt) {\n return {\n status: 400,\n body: {\n challengeId: payload.challengeId ?? '',\n acknowledged: false,\n pendingCounterparties: [],\n respondedAt: new Date().toISOString(),\n error: 'Missing required challenge fields',\n },\n };\n }\n\n // Check if challenge has expired\n const now = new Date();\n const expiresAt = new Date(payload.expiresAt);\n if (now > expiresAt) {\n return {\n status: 410,\n body: {\n challengeId: payload.challengeId,\n acknowledged: false,\n pendingCounterparties: [],\n respondedAt: now.toISOString(),\n error: 'Challenge has expired',\n },\n };\n }\n\n // Respond with current pending list\n return {\n status: 200,\n body: {\n challengeId: payload.challengeId,\n acknowledged: true,\n pendingCounterparties: this.getPendingList(),\n respondedAt: now.toISOString(),\n },\n };\n }\n}\n","/**\n * PDLSS Formatter — Transport Format Conversion\n *\n * Converts between full PDLSS boundaries and compact transport format\n * used in HTTP headers, A2A metadata, and MCP _meta blocks.\n */\n\nimport type { AstraSyncCredentials } from '../types';\n\n/**\n * Full PDLSS configuration (as returned by the backend).\n */\nexport interface PDLSSConfig {\n purpose?: {\n categories?: string[];\n allowedActions?: string[];\n deniedActions?: string[];\n };\n duration?: {\n maxSessionDuration?: number;\n ttl?: number;\n allowedDays?: number[];\n allowedHours?: { start: number; end: number };\n };\n limits?: {\n autonomousThreshold?: number;\n stepUpThreshold?: number;\n approvalThreshold?: number;\n currency?: string;\n };\n scope?: {\n jurisdictions?: string[];\n resources?: string[];\n resourceTypes?: string[];\n };\n selfInstantiation?: {\n allowed: boolean;\n maxDepth?: number;\n maxSubAgents?: number;\n };\n}\n\n/**\n * Compact transport format (embedded in headers/metadata).\n */\nexport type TransportPDLSS = NonNullable<AstraSyncCredentials['pdlss']>;\n\n/**\n * Convert full PDLSS boundaries into compact transport format.\n * Used by AgentClient when building credential headers/metadata.\n */\nexport function formatPDLSSForTransport(pdlss: PDLSSConfig): TransportPDLSS {\n const transport: TransportPDLSS = {};\n\n // Purpose: pick the primary category and first allowed action\n if (pdlss.purpose?.categories?.length) {\n transport.purpose = {\n category: pdlss.purpose.categories[0],\n action: pdlss.purpose.allowedActions?.[0],\n };\n }\n\n // Duration: use the shorter of maxSessionDuration and ttl\n if (pdlss.duration) {\n const candidates: number[] = [];\n if (pdlss.duration.maxSessionDuration) candidates.push(pdlss.duration.maxSessionDuration);\n if (pdlss.duration.ttl) candidates.push(pdlss.duration.ttl);\n if (candidates.length > 0) {\n transport.duration = { maxSessionDuration: Math.min(...candidates) };\n }\n }\n\n // Scope: use the primary jurisdiction\n if (pdlss.scope?.jurisdictions?.length) {\n transport.scope = { jurisdiction: pdlss.scope.jurisdictions[0] };\n }\n\n return transport;\n}\n\n/**\n * Parse transport format back into full PDLSS config.\n * Used by counterparty-side when receiving credentials.\n */\nexport function parsePDLSSFromTransport(transport: TransportPDLSS): PDLSSConfig {\n const pdlss: PDLSSConfig = {};\n\n if (transport.purpose) {\n pdlss.purpose = {\n categories: [transport.purpose.category],\n allowedActions: transport.purpose.action ? [transport.purpose.action] : undefined,\n };\n }\n\n if (transport.duration) {\n pdlss.duration = {\n maxSessionDuration: transport.duration.maxSessionDuration,\n };\n }\n\n if (transport.scope) {\n pdlss.scope = {\n jurisdictions: transport.scope.jurisdiction ? [transport.scope.jurisdiction] : undefined,\n };\n }\n\n return pdlss;\n}\n","/**\n * Decision Client — Counterparty-Side Decision Recording\n *\n * Helper for counterparties to record their grant/deny decisions\n * back to AstraSync after receiving a verification result.\n */\n\nimport type { GatewayConfig } from '../types';\n\ninterface RecordDecisionParams {\n sessionId: string;\n decision: 'granted' | 'denied';\n reason?: string;\n tokenIssued?: boolean;\n auditId?: string;\n}\n\ninterface RecordDecisionResult {\n recorded: boolean;\n blockchainTxHash?: string;\n}\n\n/**\n * Record a counterparty's grant/deny decision for a verification session.\n * POST to /agents/verify-access/:sessionId/decision\n */\nexport async function recordDecision(\n config: GatewayConfig,\n params: RecordDecisionParams,\n): Promise<RecordDecisionResult> {\n const { sessionId, ...body } = params;\n const baseUrl = config.apiBaseUrl.replace(/\\/$/, '');\n const url = `${baseUrl}/agents/verify-access/${encodeURIComponent(sessionId)}/decision`;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n }\n\n if (config.customHeaders) {\n Object.assign(headers, config.customHeaders);\n }\n\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'Unknown error');\n throw new Error(\n `Failed to record decision for session ${sessionId}: ${response.status} ${errorText}`,\n );\n }\n\n const result = await response.json();\n\n return {\n recorded: result.recorded ?? true,\n blockchainTxHash: result.blockchainTxHash,\n };\n}\n"],"mappings":"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","names":["decodeSdJwtSync","createHash","createHash"]}