@astrasyncai/verification-gateway 5.4.2 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -21
- package/dist/adapters/express.d.mts +1 -1
- package/dist/adapters/express.d.ts +1 -1
- package/dist/adapters/express.js +12 -3
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +12 -3
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +1 -1
- package/dist/adapters/mcp.d.ts +1 -1
- package/dist/adapters/mcp.js +12 -3
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +12 -3
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +1 -1
- package/dist/adapters/nextjs.d.ts +1 -1
- package/dist/adapters/nextjs.js +12 -3
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +12 -3
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +41 -1
- package/dist/adapters/sdk.d.ts +41 -1
- package/dist/adapters/sdk.js +54 -3
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +54 -3
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.js +1 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +1 -1
- package/dist/agent/index.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +12 -3
- package/dist/bin/astrasync-codex-hook.js +12 -3
- package/dist/bin/astrasync-guard.js +12 -3
- package/dist/bin/astrasync.js +13 -3
- package/dist/browser/background.js +12 -3
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +12 -3
- package/dist/browser/background.mjs.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/cli/index.mjs.map +1 -1
- package/dist/codex/index.js +12 -3
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +12 -3
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/extension.js +12 -3
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +12 -3
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +1 -1
- package/dist/edge-config.d.ts +1 -1
- package/dist/edge-config.js +1 -1
- package/dist/edge-config.js.map +1 -1
- package/dist/edge-config.mjs +1 -1
- package/dist/edge-config.mjs.map +1 -1
- package/dist/edge-core/index.d.mts +1 -1
- package/dist/edge-core/index.d.ts +1 -1
- package/dist/edge-core/index.js +12 -3
- package/dist/edge-core/index.js.map +1 -1
- package/dist/edge-core/index.mjs +12 -3
- package/dist/edge-core/index.mjs.map +1 -1
- package/dist/gateway/gateway.js +12 -3
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +12 -3
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +1 -1
- package/dist/git-trigger/git-hooks.d.ts +1 -1
- package/dist/index.d.mts +121 -4
- package/dist/index.d.ts +121 -4
- package/dist/index.js +54 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +54 -3
- package/dist/index.mjs.map +1 -1
- package/dist/registration/index.js +1 -1
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs +1 -1
- package/dist/registration/index.mjs.map +1 -1
- package/dist/transport/index.js +1 -1
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +1 -1
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-Bd2O3eX1.d.mts → types-BCmHFdkJ.d.mts} +78 -1
- package/dist/{types-BU04qAAR.d.mts → types-BK_pRNSs.d.mts} +78 -1
- package/dist/{types-BU04qAAR.d.ts → types-BK_pRNSs.d.ts} +78 -1
- package/dist/{types-DMChboN_.d.ts → types-CwY5IY-0.d.ts} +78 -1
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/verify.d.mts +1 -1
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js +12 -3
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +12 -3
- package/dist/verify.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/version.ts","../../src/http.ts","../../src/trust-levels.ts","../../src/well-known.ts","../../src/verify.ts","../../src/metadata-capture.ts","../../src/adapters/approval-gate.ts","../../src/adapters/stepup-poll.ts","../../src/verified-hop.ts","../../src/transport/mcp-server.ts","../../src/adapters/mcp.ts"],"sourcesContent":["/**\n * Single source-of-truth for the SDK's\n * package version emitted on verify-access bodies (and any future\n * telemetry). Bumped alongside `package.json#version` on every release.\n *\n * Why a constant rather than `import pkg from '../package.json'`:\n * - `tsconfig.json` sets `rootDir: ./src`; importing the sibling\n * package.json fails the build with \"outside rootDir\".\n * - Build-time string replacement (tsup `define`, esbuild banner, etc.)\n * adds toolchain coupling for a trivial gain.\n * - Embedded readonly constant works in every environment (Node, browser,\n * bundlers, Deno) without runtime fs / network access.\n *\n * Release discipline: a CI lint can grep `package.json#version` against\n * this constant if the two ever diverge in the wild. A manual bump\n * is fine — bumping both in the release-ceremony commit keeps them\n * lockstep.\n */\nexport const SDK_VERSION = '5.4.1';\n","/**\n * Shared outbound-HTTP wrapper — every request the SDK makes to the\n * AstraSync backend (or anywhere else) goes through `sdkFetch` so it\n * carries an identifying User-Agent.\n *\n * Why: Node's global fetch defaults to `user-agent: node`, which is\n * indistinguishable from every other Node client on the wire. Visit\n * Intelligence observed our own SDK/beacon traffic as anonymous \"node\"\n * visits. Self-identification is the platform's own medicine.\n *\n * Browser note: some browsers filter the User-Agent request header\n * silently (never throwing), so this is a no-op there — which is correct:\n * in a browser the page's real UA is the honest identity.\n */\n\nimport { SDK_VERSION } from './version';\n\nexport const SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;\n\n/**\n * Add the SDK User-Agent without changing the SHAPE of the caller's headers\n * (plain records stay plain records) — interceptors, adapters, and tests\n * read `init.headers` as a record, and `Headers` would also lowercase names.\n */\nfunction withUserAgent(initHeaders: HeadersInit | undefined): HeadersInit {\n if (!initHeaders) return { 'user-agent': SDK_USER_AGENT };\n if (initHeaders instanceof Headers) {\n const copy = new Headers(initHeaders);\n if (!copy.has('user-agent')) copy.set('user-agent', SDK_USER_AGENT);\n return copy;\n }\n if (Array.isArray(initHeaders)) {\n const has = initHeaders.some(([name]) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : [...initHeaders, ['user-agent', SDK_USER_AGENT]];\n }\n const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : { 'user-agent': SDK_USER_AGENT, ...initHeaders };\n}\n\nexport const sdkFetch: typeof fetch = (input, init) =>\n fetch(input, { ...init, headers: withUserAgent(init?.headers) });\n","/**\n * AstraSync Universal Verification Gateway — Trust Level helpers.\n *\n * SDK 5.0.0: the `accessLevel` band (`none`/`restricted`/`read-only`/`standard`/\n * `full`/`internal`) was removed entirely — it never gated the access decision\n * (the server does, from a verified ID + runtime challenge + PDLSS match + trust\n * score). What remains here is the coarse trust-level badge derived from the\n * numeric trust score, used only for display.\n */\n\nimport type { TrustLevel } from './types';\n\n/**\n * Trust level score ranges\n */\nexport const TRUST_LEVEL_RANGES: Record<TrustLevel, { min: number; max: number }> = {\n BRONZE: { min: 0, max: 39 },\n SILVER: { min: 40, max: 59 },\n GOLD: { min: 60, max: 79 },\n PLATINUM: { min: 80, max: 100 },\n};\n\n/**\n * Determine trust level from score\n */\nexport function getTrustLevel(score: number): TrustLevel {\n if (score >= 80) return 'PLATINUM';\n if (score >= 60) return 'GOLD';\n if (score >= 40) return 'SILVER';\n return 'BRONZE';\n}\n","import { sdkFetch } from './http';\n/**\n * SDK-side discovery of canonical platform URLs via `/.well-known/agentic-commerce`.\n *\n * Fire-and-forget pre-fetch at middleware creation; first verify() awaits\n * the in-flight promise if it hasn't resolved. 60-minute TTL with\n * stale-while-revalidate background refresh.\n */\n\nexport interface WellKnownAgenticCommerce {\n registrationUrl: string;\n documentationUrl: string;\n verifyAccessUrl: string;\n /** 5.1.0: hosted AstraSync MCP bridge endpoint (absent on self-hosted backends). */\n mcpEndpoint?: string;\n /** 5.1.0: bridge connector-discovery doc (absent on self-hosted backends). */\n mcpDiscoveryUrl?: string;\n}\n\ninterface CacheEntry {\n data: WellKnownAgenticCommerce;\n fetchedAt: number;\n}\n\nconst CACHE_TTL_MS = 60 * 60 * 1000; // 60 minutes\n\nconst cache = new Map<string, CacheEntry>();\nconst inflight = new Map<string, Promise<WellKnownAgenticCommerce>>();\n\nfunction wellKnownUrl(apiBaseUrl: string): string {\n // apiBaseUrl is typically 'https://astrasync.ai/api' — strip the /api suffix\n // to get the platform root, then append /.well-known/agentic-commerce.\n const base = apiBaseUrl.replace(/\\/api\\/?$/, '');\n return `${base}/.well-known/agentic-commerce`;\n}\n\nasync function fetchWellKnown(apiBaseUrl: string): Promise<WellKnownAgenticCommerce> {\n const url = wellKnownUrl(apiBaseUrl);\n const response = await sdkFetch(url, {\n method: 'GET',\n headers: { Accept: 'application/json' },\n signal: AbortSignal.timeout(5000),\n });\n if (!response.ok) {\n throw new Error(\n `AstraSync platform must expose /.well-known/agentic-commerce; ` +\n `got ${response.status} from ${url}. SDK cannot initialise without it.`\n );\n }\n const data = (await response.json()) as Record<string, unknown>;\n if (!data.registrationUrl || !data.documentationUrl || !data.verifyAccessUrl) {\n throw new Error(\n `/.well-known/agentic-commerce response missing required fields ` +\n `(registrationUrl, documentationUrl, verifyAccessUrl).`\n );\n }\n return data as unknown as WellKnownAgenticCommerce;\n}\n\n/**\n * Start a background fetch. Returns the promise for callers that need\n * to await it (first verify() call).\n */\nexport function prefetchWellKnown(apiBaseUrl: string): Promise<WellKnownAgenticCommerce> {\n const existing = inflight.get(apiBaseUrl);\n if (existing) return existing;\n\n const promise = fetchWellKnown(apiBaseUrl)\n .then((data) => {\n cache.set(apiBaseUrl, { data, fetchedAt: Date.now() });\n inflight.delete(apiBaseUrl);\n return data;\n })\n .catch((err) => {\n inflight.delete(apiBaseUrl);\n throw err;\n });\n\n inflight.set(apiBaseUrl, promise);\n return promise;\n}\n\n/**\n * Get cached well-known URLs. If stale, triggers a background refresh\n * and returns stale data (stale-while-revalidate). If no cache exists,\n * awaits the in-flight fetch or starts a new one.\n */\nexport async function getWellKnownUrls(apiBaseUrl: string): Promise<WellKnownAgenticCommerce> {\n const entry = cache.get(apiBaseUrl);\n\n if (entry) {\n if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {\n // Stale — trigger background refresh, return stale data\n prefetchWellKnown(apiBaseUrl).catch(() => {\n // Background refresh failed — stale data remains until next request\n });\n }\n return entry.data;\n }\n\n // No cache — must await\n const pending = inflight.get(apiBaseUrl);\n if (pending) return pending;\n\n return prefetchWellKnown(apiBaseUrl);\n}\n\n/**\n * Synchronous cache read — returns cached URLs or undefined.\n * Never triggers a fetch. Used by verify() to avoid extra HTTP calls\n * in the hot path; adapters are responsible for prefetching.\n */\nexport function getCachedWellKnownUrls(apiBaseUrl: string): WellKnownAgenticCommerce | undefined {\n return cache.get(apiBaseUrl)?.data;\n}\n\n/** Reset cache — for testing only. */\nexport function _resetWellKnownCache(): void {\n cache.clear();\n inflight.clear();\n}\n","/**\n * AstraSync Universal Verification Gateway - Core Verification Logic\n *\n * This module handles the core verification logic, calling the AstraSync API\n * and processing the response into a standardized VerificationResult.\n */\n\nimport { sdkFetch } from './http';\nimport type {\n GatewayConfig,\n AgentCredentials,\n VerificationRequest,\n VerificationResult,\n VerifiedAgent,\n VerifiedDeveloper,\n VerifiedOrganization,\n GuidanceInfo,\n EnhancedVerificationResult,\n TokenGuidance,\n RuntimeChallengeResult,\n StepUpApprovalInfo,\n SettlementOutcomeInfo,\n SettlementArtifact,\n AttemptReport,\n} from './types';\nimport { getTrustLevel } from './trust-levels';\nimport type { ObservedMetadata } from './metadata-capture';\nimport { SDK_VERSION } from './version';\nimport { getCachedWellKnownUrls, type WellKnownAgenticCommerce } from './well-known';\n\n/**\n * Default configuration values\n *\n * apiBaseUrl matches the OpenAPI authoritative server (https://astrasync.ai/api\n * for prod, https://staging.astrasync.ai/api for staging). Always include the\n * /api path prefix when overriding — registration / docs URLs are derived by\n * stripping it.\n */\nconst DEFAULT_CONFIG: Partial<GatewayConfig> = {\n apiBaseUrl: 'https://astrasync.ai/api',\n // minTrustScore + minTrustScoreForFull deprecated in v2.3.0 — server decides.\n // cacheTtl deliberately unset. When undefined, cacheResult applies the 60s\n // default; the caller's explicit value is honoured for cacheable results.\n // Denials and step-up escalations are NEVER cached (3.6.0) regardless of\n // cacheTtl. Set cacheTtl: 0 to disable caching entirely.\n debug: false,\n};\n\n/**\n * Init self-test state. Fires once per process on first verify() call to warn\n * if apiBaseUrl is pointing at the wrong host (e.g. a marketing site that\n * 200s with text/html instead of the API).\n */\nlet initCheckPerformed = false;\n\n/** Test-only: re-arm the one-time init self-check. */\nexport function _resetInitCheck(): void {\n initCheckPerformed = false;\n}\n\n/** One-shot guard for v2.3.0 deprecation warning. */\nlet deprecationWarningShown = false;\n\nasync function performInitCheck(\n apiBaseUrl: string,\n debug?: boolean,\n strictInit?: boolean,\n apiKey?: string\n): Promise<void> {\n initCheckPerformed = true;\n try {\n const probeUrl = `${apiBaseUrl}/agents/verify-access`;\n // HEAD mirrors GET semantics (running the full request pipeline without a\n // body) so the response carries the same content-type the marketing 404\n // would return. OPTIONS often gets short-circuited by CORS-preflight\n // handlers and returns no content-type, defeating the check.\n const response = await sdkFetch(probeUrl, { method: 'HEAD' });\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.startsWith('text/html')) {\n const message =\n `[VerificationGateway] apiBaseUrl '${apiBaseUrl}' returned HTML (content-type: ${contentType}). ` +\n `This usually means apiBaseUrl is pointing at a marketing site instead of the API. ` +\n `Expected: 'https://astrasync.ai/api' (prod) or 'https://staging.astrasync.ai/api' (staging).`;\n // Audit F-A6-33: when strictInit is set, throw on misconfig instead\n // of warning + continuing. Recurring footgun per\n // `[[feedback_astrasync_api_url_canonical]]` — silent warn lets\n // misconfigured prod deploys ship with all verify-access calls\n // silently failing.\n if (strictInit) {\n throw new Error(`${message} (strictInit=true)`);\n }\n console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);\n } else if (debug) {\n console.log(\n `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`\n );\n }\n\n // 4.0.0: key liveness probe. A dead API key otherwise surfaces only\n // per-request as inbound agents get turned away — the operator should\n // learn at deploy time. 404 = older backend without self-check: silent.\n if (apiKey) {\n const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check`, {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n if (selfCheck.status === 401) {\n const body = (await selfCheck.json().catch(() => ({}))) as {\n code?: string;\n message?: string;\n remediation?: { manageUrl?: string };\n };\n const message =\n `[VerificationGateway] the configured AstraSync API key was REJECTED at startup ` +\n `(${body.code ?? 'api_key_invalid'}: ${body.message ?? 'invalid key'}). Every inbound agent ` +\n `will be turned away until the key is rotated` +\n (body.remediation?.manageUrl ? ` at ${body.remediation.manageUrl}` : '') +\n `.`;\n if (strictInit) {\n throw new Error(`${message} (strictInit=true)`);\n }\n console.error(message);\n } else if (selfCheck.ok) {\n const body = (await selfCheck.json().catch(() => ({}))) as {\n daysToExpiry?: number | null;\n };\n if (typeof body.daysToExpiry === 'number' && body.daysToExpiry <= 7) {\n console.warn(\n `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) — ` +\n `rotate it at Settings → Developer Tools before integrations start failing.`\n );\n } else if (debug) {\n console.log('[VerificationGateway] API key self-check passed');\n }\n }\n }\n } catch (err) {\n if (strictInit) {\n // strict mode propagates init failures so callers get a clear,\n // synchronous \"your config is broken\" signal at startup.\n throw err;\n }\n if (debug) {\n console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);\n }\n }\n}\n\n/**\n * Simple in-memory cache for verification results\n */\nconst verificationCache = new Map<string, { result: VerificationResult; expiresAt: number }>();\n\n/**\n * Generate cache key from the full request shape.\n *\n * Historically the key was credentials-only (`astraId-apiKey-jwt`),\n * which silently returned the cached result of a prior call even when the\n * subsequent call's purpose/action/counterparty/etc. differed. That was a\n * security gap — a warm cache could serve an allow verdict for a purpose the\n * agent hadn't actually been authorised for. Key now includes every field\n * that affects the backend verdict.\n *\n * MAINTAIN: when adding a field to VerificationRequest that affects the\n * backend verdict, add it to this key. Fields that don't affect verdict\n * (e.g. requestId, timestamps, sdkVersion) don't belong here — including\n * them would needlessly thrash the cache for repeated identical requests.\n *\n * 3.12.0 (Trust Record): `attemptId` and `considerationSet` are\n * deliberately EXCLUDED — like `callerMetadata.observedMetadata` they are\n * observational pass-through that never affects the verdict, and keying on\n * a per-attempt id would defeat verdict caching entirely. Cache hits get\n * their attemptId rewritten in verify() instead (never leak a prior\n * attempt's id from a cached result).\n */\nfunction getCacheKey(request: VerificationRequest, counterpartyId?: string): string {\n const c = request.credentials;\n return [\n c.astraId || '',\n c.apiKey || '',\n c.jwt || '',\n request.purpose || '',\n request.action || '',\n request.resourceType || '',\n request.resource || '',\n request.jurisdiction || '',\n request.transactionValue ?? '',\n request.currency || '',\n // SECURITY (cross-merchant cache leak): the merchant identity is sent via\n // `config.counterpartyId`, NOT on the request, so it was previously absent\n // from the key — two verifies for the SAME agent/purpose/action/value but\n // DIFFERENT merchants collided, and a grant at a permissive merchant (low\n // trust floor) was served for a stricter one. Same bug class as the\n // duration omission (F-A1-07). counterpartyId affects the backend verdict\n // (trust floor / per-route policy), so it MUST key the cache.\n counterpartyId || '',\n request.counterpartyUrl || '',\n request.counterpartyType || '',\n request.isSubAgentRequest ? '1' : '0',\n request.parentAgentId || '',\n request.subAgentDepth ?? '',\n // Audit F-A1-07: previously-missing dimensions that DO affect the\n // backend verdict. Without these, two requests with different\n // durations (e.g. 60s vs 86400s) collided on the same cache key and\n // the shorter-duration allow served the longer-duration request.\n request.durationRequired ?? '',\n request.invocationProtocol || '',\n request.enableRuntimeChallenge ? '1' : '0',\n // callerMetadata fields contribute to risk model; include the ones\n // backend reads. sourceIp/userAgent/forwardedFor change per-request\n // so their inclusion effectively forces a re-check for any varying\n // client (the right behavior — IP-driven anomaly scoring shouldn't\n // be cached across IPs).\n request.callerMetadata?.sourceIp || '',\n request.callerMetadata?.userAgent || '',\n request.callerMetadata?.forwardedFor || '',\n request.callerMetadata?.agentCardUrl || '',\n // UAT2 Bug B (5.4.1): the commerce phase and cart identity DO affect the\n // backend verdict — the confirm leg is the ONLY leg that may execute\n // first-party settlement. Without these, a confirm within the cache TTL of\n // its start_checkout quote was served the cached quote-leg grant (no\n // settlementOutcome) and never reached the backend. Same F-A1-07 bug class.\n request.commercePhase || '',\n request.checkoutSessionId || '',\n ].join('|');\n}\n\n/**\n * Check if cached result is still valid\n */\nfunction getCachedResult(\n request: VerificationRequest,\n counterpartyId?: string\n): VerificationResult | null {\n const key = getCacheKey(request, counterpartyId);\n const cached = verificationCache.get(key);\n\n if (cached && cached.expiresAt > Date.now()) {\n return cached.result;\n }\n\n if (cached) {\n verificationCache.delete(key);\n }\n\n return null;\n}\n\n/**\n * Cache a verification result.\n *\n * 3.6.0: uniform 60s default. The earlier step-up TTL split (300s for\n * step-up verdicts) is gone — step-up verdicts are no longer cached AT ALL\n * (see the write predicate in verify()): a cached step_up_required result\n * carries a pollUrl that goes stale (consumed/expired) and, worse, keeps\n * denying retries that the owner has already approved. When `configuredTtl`\n * is set (truthy positive number), it's honoured for cacheable results.\n */\nconst DEFAULT_CACHE_TTL_SECONDS = 60;\n\nfunction cacheResult(\n request: VerificationRequest,\n result: VerificationResult,\n configuredTtl: number | undefined,\n counterpartyId?: string\n): void {\n const ttlSeconds = configuredTtl && configuredTtl > 0 ? configuredTtl : DEFAULT_CACHE_TTL_SECONDS;\n const key = getCacheKey(request, counterpartyId);\n verificationCache.set(key, {\n result,\n expiresAt: Date.now() + ttlSeconds * 1000,\n });\n}\n\n/**\n * Clear the verification cache\n */\nexport function clearCache(): void {\n verificationCache.clear();\n}\n\n/**\n * Extract agent credentials from various sources\n */\nexport function extractCredentials(\n headers: Record<string, string | string[] | undefined>,\n query?: Record<string, string | undefined>\n): AgentCredentials {\n const credentials: AgentCredentials = {};\n\n // Audit F-A1-12: ASTRA-ID shape validation. Accept only the canonical\n // pattern (ASTRA-/ASTRAE- prefix + 1-64 url-safe chars) to prevent CRLF\n // header injection or downstream interpolation surprises. Reject otherwise.\n const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;\n\n // Check for ASTRA-ID in headers (case-insensitive). Accepts the historical\n // X-Astra-Id name plus the X-Astra-AgentId / x-astra-agent-id alias the\n // partner asked for in #9a — both surface the same field.\n const astraIdHeader =\n headers['x-astra-id'] ||\n headers['X-Astra-Id'] ||\n headers['X-ASTRA-ID'] ||\n headers['x-astra-agentid'] ||\n headers['X-Astra-AgentId'] ||\n headers['x-astra-agent-id'] ||\n headers['X-Astra-Agent-Id'] ||\n headers['X-ASTRA-AGENT-ID'];\n if (astraIdHeader) {\n // Audit F-A1-11: guard array-confusion. If multiple X-Astra-Id headers\n // were sent (Express collapses to comma-joined string on most paths),\n // take only the first canonical-shaped one.\n const raw = Array.isArray(astraIdHeader)\n ? astraIdHeader[0]\n : typeof astraIdHeader === 'string'\n ? astraIdHeader\n : undefined;\n if (typeof raw === 'string' && ASTRA_ID_PATTERN.test(raw)) {\n credentials.astraId = raw;\n }\n }\n\n // Check for API key in headers\n const apiKeyHeader = headers['x-api-key'] || headers['X-Api-Key'] || headers['X-API-KEY'];\n if (apiKeyHeader) {\n credentials.apiKey = Array.isArray(apiKeyHeader) ? apiKeyHeader[0] : apiKeyHeader;\n }\n\n // Check Authorization header for Bearer token.\n // Audit F-A1-11: defensive type guard — Array.isArray gives a non-string,\n // and Express path quirks can produce undefined despite the truthy check.\n const authHeader = headers['authorization'] || headers['Authorization'];\n if (authHeader) {\n const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;\n if (typeof authValue === 'string') {\n credentials.authorizationHeader = authValue;\n if (authValue.startsWith('Bearer ')) {\n credentials.jwt = authValue.slice(7);\n }\n }\n }\n\n // Check query parameters as fallback\n if (query) {\n if (query.astraId && !credentials.astraId) {\n credentials.astraId = query.astraId;\n }\n if (query.apiKey && !credentials.apiKey) {\n credentials.apiKey = query.apiKey;\n }\n }\n\n return credentials;\n}\n\n/**\n * Check if credentials are present\n */\nexport function hasCredentials(credentials: AgentCredentials): boolean {\n return !!(credentials.astraId || credentials.apiKey || credentials.jwt);\n}\n\n/**\n * Source of a synthesised guidance response. The sources are split — the\n * `'no_credentials'` shape is the original (caller has no AstraSync\n * credentials, suggest registration). The `'api_error'` shape is for when\n * the verify-access HTTP call itself failed (5xx, network, etc.) — DON'T\n * tell a verified-but-currently-blocked partner to \"register your agent\".\n *\n * 4.0.0 adds `'merchant_misconfig'`: the backend rejected THIS INTEGRATION'S\n * own API key (401 api_key_revoked/expired/invalid). Neither the agent's\n * fault nor a platform outage — retrying won't help, and the inbound agent\n * deserves to know the site operator's integration is broken (the\n * gadget-emporium failure mode: a rotated key silently 401ing every\n * credentialed agent for days).\n */\ntype GuidanceSource = 'no_credentials' | 'api_error' | 'merchant_misconfig';\n\n/**\n * MCP-connector steering (5.1.0). Present only when the backend's well-known\n * doc advertises a hosted bridge. The copy must directly counter the observed\n * model refusal (\"go register manually on the dashboard\"): registration via\n * the connector is the agent's own next action, with the human approval built\n * into the flow.\n */\nfunction mcpGuidance(urls?: WellKnownAgenticCommerce): GuidanceInfo['mcp'] {\n if (!urls?.mcpEndpoint) return undefined;\n return {\n endpoint: urls.mcpEndpoint,\n discoveryUrl: urls.mcpDiscoveryUrl,\n message:\n 'If you are operating inside an MCP-capable host (Claude, ChatGPT, Cursor, or any MCP client), ' +\n 'add the AstraSync MCP connector instead of integrating these URLs by hand: registration through ' +\n 'the connector is KEYLESS and HUMAN-APPROVED — your human approves in an in-chat card, and you ' +\n 'never see, hold, or transmit an API key, password, or private key.',\n };\n}\n\n/**\n * Create guidance response for unverified agents or for API-error fallback.\n *\n * The source is split so the `register your agent` template\n * doesn't fire on transient backend failures. Also threads `correlationId`\n * through so adapter `onDenied` handlers can surface it on the merchant's\n * response body for log correlation.\n */\nfunction createGuidanceResponse(\n _config: GatewayConfig,\n reason?: string,\n options: {\n source?: GuidanceSource;\n correlationId?: string;\n /** 3.12.0 (Trust Record): server-echoed attempt handle, threaded like correlationId. */\n attemptId?: string;\n urls?: WellKnownAgenticCommerce;\n /** 4.0.0: backend-provided remediation URLs for the merchant (misconfig source). */\n remediation?: { docsUrl?: string; manageUrl?: string };\n } = {}\n): VerificationResult {\n const source = options.source ?? 'no_credentials';\n const isApiError = source === 'api_error';\n const isMisconfig = source === 'merchant_misconfig';\n const urls = options.urls;\n\n const guidance: GuidanceInfo = isMisconfig\n ? {\n message:\n \"This site's AstraSync integration is misconfigured (its API key was revoked, expired, or rotated). \" +\n 'Your identity and the AstraSync platform are fine — contact the site operator. Retrying will not help.',\n registrationUrl: urls?.registrationUrl ?? '',\n documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? '',\n steps: [\n 'Report the failure to the site operator — their AstraSync API key needs rotating',\n 'Do not retry: the failure is deterministic until the operator updates their key',\n ],\n }\n : isApiError\n ? {\n message:\n 'Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.',\n registrationUrl: urls?.registrationUrl ?? '',\n documentationUrl: urls?.documentationUrl ?? '',\n steps: [\n 'Retry the request with exponential backoff',\n 'If failures persist, share the correlationId with support',\n ],\n }\n : {\n message:\n 'This service verifies AI agents before granting access. Please register your agent with AstraSync.',\n registrationUrl: urls?.registrationUrl ?? '',\n documentationUrl: urls?.documentationUrl ?? '',\n steps: [\n // MCP steering leads when the hosted bridge is advertised — the\n // connector is the agent-actionable path (keyless, human-approved).\n ...(urls?.mcpEndpoint\n ? [\n `Fastest path (inside an MCP host): add the AstraSync MCP connector at ${urls.mcpEndpoint} — keyless, human-approved registration`,\n ]\n : []),\n 'Register for an AstraSync account',\n 'Create and register your agent',\n 'Add your ASTRA-ID to request headers',\n 'Retry your request',\n ],\n ...(mcpGuidance(urls) && { mcp: mcpGuidance(urls) }),\n };\n\n return {\n // Round-18 G4: createGuidanceResponse fires for unverified-agent path or\n // API-error fallback. Identity is not verified (no agent resolved);\n // policy is not evaluated (we never reached the gate).\n identityVerified: false,\n policyAllowed: false,\n guidance,\n denialReasons: reason ? [reason] : ['No valid agent credentials provided'],\n // Round-10 (#47, O5): on API-error fallback, surface a typed failure so\n // partners (and their custom onDenied handlers) can branch on\n // dimension. Without this, the synthesised stub was indistinguishable\n // from a real policy deny.\n failures: isMisconfig\n ? [\n {\n dimension: 'verify_access.merchant_misconfigured',\n message: reason ?? 'The integration API key was rejected by AstraSync',\n guidance: guidance.message,\n },\n ]\n : isApiError\n ? [\n {\n dimension: 'verify_access.api_error',\n message: reason ?? 'Verification temporarily unavailable',\n guidance: guidance.message,\n },\n ]\n : undefined,\n // 4.0.0: typed marker so adapters render the misconfig envelope (503 +\n // MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.\n ...(isMisconfig && { misconfigured: true }),\n // 4.6.0: a transient api_error is retryable — mark it so consumers surface\n // \"temporarily unavailable, retry\" (recommendation 'error') instead of\n // coercing the absent recommendation to a policy 'deny'. Misconfig is\n // deterministic and intentionally NOT marked retryable.\n ...(isApiError && { retryable: true }),\n correlationId: options.correlationId,\n attemptId: options.attemptId,\n verifiedAt: new Date(),\n };\n}\n\n/**\n * Call the AstraSync verify-access API\n */\nasync function callVerifyAccessAPI(\n config: GatewayConfig,\n request: VerificationRequest\n): Promise<{\n success: boolean;\n access?: {\n allowed: boolean;\n reason?: string;\n /**\n * Aggregated denial failures (v2.9.8+). Empty / absent when allowed.\n * Each entry is `{ dimension, message, guidance? }` — see\n * `AccessFailure` for the contract.\n */\n failures?: Array<{ dimension: string; message: string; guidance?: string }>;\n requiresStepUp?: boolean;\n requiresApproval?: boolean;\n appliedPolicy?: {\n boundaryName: string;\n policyVersion: string;\n };\n counterparty?: {\n id: string;\n name: string;\n trustScoreRequirement: number;\n };\n };\n agent?: {\n kyaAgentId: string;\n astraId: string;\n name: string;\n trustScore: number;\n trustLevel: string;\n agentStatus: string;\n blockchainStatus: string;\n };\n developer?: {\n kyaOwnerId: string;\n fullName: string;\n email: string;\n identityVerified: boolean;\n trustScore: number;\n };\n organization?: {\n name: string;\n verified: boolean;\n trustScore: number;\n };\n /**\n * Structured explanation of the verification decision. Tells the merchant\n * WHY (id verified? challenge passed? request within PDLSS? trust score?)\n * without exposing thresholds, scope lists, or other-tenant counterparty\n * membership. Empty `attestations` unless the endpoint's access policy\n * declared `required_attestations`.\n */\n verificationContext?: {\n idVerified: boolean;\n runtimeChallenge: {\n status: 'passed' | 'skipped' | 'failed' | 'timeout' | 'not_supported';\n checkedAt: string | null;\n };\n pdlssCheck: {\n result: 'within' | 'exceeded' | 'denied' | 'not_evaluated';\n purpose: 'approved' | 'denied';\n scope: 'approved' | 'denied';\n };\n dynamicTrustScore: number;\n attestations: Array<{\n type: string;\n status: 'passed' | 'failed';\n /** LMAX-POC C1: underlying check timestamp for merchant freshness gates. */\n checkedAt: string;\n validUntil?: string;\n proofType: 'reference' | 'zkp';\n proof: string;\n }>;\n };\n error?: string;\n /**\n * 4.0.0: the backend 401'd this integration's OWN API key (revoked /\n * expired / invalid) — deterministic merchant-side misconfig, not a\n * transient failure. `misconfigCode` carries the backend's api_key_* code\n * and `remediation` its operator-facing URLs when present.\n */\n misconfig?: boolean;\n misconfigCode?: string;\n remediation?: { docsUrl?: string; manageUrl?: string };\n /**\n * When the verify-access server response carries a\n * correlationId on an error envelope, propagate it so the SDK can thread\n * it through createGuidanceResponse → adapter onDenied → merchant body.\n */\n correlationId?: string;\n /**\n * 3.12.0 (Trust Record): the server echoes the attempt-chain\n * handle top-level on every response branch (error envelopes included).\n */\n attemptId?: string;\n}> {\n const { credentials, ...requestData } = request;\n\n // Build the request body. agentId is omitted when not provided so the\n // server treats it as an anonymous canonical-flow call (Branch A/B/C).\n const body: Record<string, unknown> = {\n ...(credentials.astraId && { agentId: credentials.astraId }),\n ...(requestData.purpose && { purpose: requestData.purpose }),\n };\n\n // Add optional fields\n if (requestData.action) body.action = requestData.action;\n if (requestData.resourceType) body.resourceType = requestData.resourceType;\n if (requestData.resource) body.resource = requestData.resource;\n if (requestData.jurisdiction) body.jurisdiction = requestData.jurisdiction;\n if (requestData.transactionValue) body.transactionValue = requestData.transactionValue;\n if (requestData.currency) body.currency = requestData.currency;\n if (requestData.isSubAgentRequest) body.isSubAgentRequest = requestData.isSubAgentRequest;\n if (requestData.parentAgentId) body.parentAgentId = requestData.parentAgentId;\n if (requestData.subAgentDepth !== undefined) body.subAgentDepth = requestData.subAgentDepth;\n // Handshake Protocol v10 additions\n if (requestData.enableRuntimeChallenge)\n body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;\n if (requestData.createSession) body.createSession = requestData.createSession;\n if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;\n if (requestData.counterpartyType) body.counterpartyType = requestData.counterpartyType;\n if (requestData.counterpartyUrl) body.counterpartyUrl = requestData.counterpartyUrl;\n if (config.counterpartyId) body.counterpartyId = config.counterpartyId;\n if (requestData.runtimeChallengeOptions)\n body.runtimeChallengeOptions = requestData.runtimeChallengeOptions;\n // Round-12 (F19): transport-vs-intent separation. MCP middleware sets\n // this to 'mcp'; non-MCP callers leave it unset.\n if (requestData.invocationProtocol) body.invocationProtocol = requestData.invocationProtocol;\n // 5.3.0 (astra-pay): checkout-leg discriminator — first-party settlement\n // fires only on 'confirm'; absent keeps pre-phase server behavior.\n if (requestData.commercePhase) body.commercePhase = requestData.commercePhase;\n // Per-cart idempotency key (checkout session id) forwarded on the confirm\n // leg so concurrent confirms for one cart charge at most once.\n if (requestData.checkoutSessionId) body.checkoutSessionId = requestData.checkoutSessionId;\n // Order-plane cart lines (receipts + fulfillment email) — informational.\n if (requestData.checkoutItems) body.checkoutItems = requestData.checkoutItems;\n // 3.9.0 (§33.5 #1): raw commerce artifacts forwarded verbatim — the\n // backend's Step 0.5 pipeline does the cryptographic verification\n // server-side (adapters forward, never verify locally).\n if (requestData.commerceArtifacts) body.commerceArtifacts = requestData.commerceArtifacts;\n // 3.12.0 (Trust Record, §33.5 #3): attempt-chain handle + consideration set,\n // forwarded skip-when-undefined (observedMetadata precedent) — pure\n // observational pass-through; the server echoes attemptId on every branch.\n if (requestData.attemptId) body.attemptId = requestData.attemptId;\n if (requestData.considerationSet) body.considerationSet = requestData.considerationSet;\n\n // Round-13 (F14 closure / R13-7): emit the SDK package version on every\n // verify-access body so the backend can auto-populate `kya_counterparty.\n // sdk_version` for the calling endpoint. Body field (not User-Agent\n // header) is the canonical channel — works in Node, browser, behind\n // Cloudflare, and across SDK bundlers without environment-specific\n // header gymnastics. Backend's `validation.ts:verifyAccessSchema`\n // accepts the semver regex; the auto-pop logic is forward-only.\n body.sdkVersion = SDK_VERSION;\n\n // Forward caller metadata when present. Merges the legacy top-level\n // clientIp/userAgent into the nested block for backward compatibility.\n if (requestData.callerMetadata || requestData.clientIp || requestData.userAgent) {\n const meta = {\n ...(requestData.clientIp && { sourceIp: requestData.clientIp }),\n ...(requestData.userAgent && { userAgent: requestData.userAgent }),\n ...requestData.callerMetadata,\n };\n if (Object.keys(meta).length > 0) body.callerMetadata = meta;\n }\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...config.customHeaders,\n };\n\n // verify-access requires authentication. Use ONLY the merchant's\n // configured apiKey for outbound auth. Previously the caller's inbound\n // Authorization header took priority, which pivoted the verify-access\n // call to the caller's identity — letting an attacker who supplied\n // their own Authorization header trigger verify-access calls under\n // THEIR account and observe the resulting verdicts for arbitrary\n // lookups they triggered via the merchant. Audit F-A1-10.\n //\n // The caller-supplied JWT (if any) is already extracted to\n // credentials.jwt and travels in the verify-access request body so\n // backend can evaluate it as the agent's identity claim — separate from\n // the merchant-attribution auth on the outbound HTTPS call.\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n // Legacy header kept for compatibility with any middleware that reads it directly.\n headers['X-API-Key'] = config.apiKey;\n }\n\n try {\n // 4.0.0: hard timeout — without one, a backend that accepts the\n // connection but never answers hangs the merchant's middleware and every\n // inbound agent request behind it (observed live on a partner property).\n // Timeouts land on the catch path below → api_error (fail-closed),\n // NOT misconfig. The edge adapter races its own tighter withBudget.\n const response = await sdkFetch(`${config.apiBaseUrl}/agents/verify-access`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 10_000),\n });\n\n const data = await response.json();\n\n // v2.3.8 (defect #29): treat 410 Gone as a deterministic deactivated-endpoint\n // signal. Older SDKs may treat any non-2xx as transient and retry; v2.3.8+\n // surfaces it as a structured denial with `reason: 'endpoint_deactivated'`\n // so callers can distinguish \"endpoint gone\" from \"endpoint denied this\".\n if (response.status === 410) {\n return {\n success: true,\n // 3.12.0 (Trust Record): keep the server-echoed attempt handle even on\n // the synthesised 410 deny shape (echoed on every branch).\n attemptId: typeof data?.attemptId === 'string' ? data.attemptId : undefined,\n access: {\n allowed: false,\n reason: 'endpoint_deactivated',\n failures: [\n {\n dimension: 'endpoint.deactivated',\n message:\n typeof data?.message === 'string' ? data.message : 'Endpoint has been deactivated',\n guidance:\n typeof data?.guidance === 'string'\n ? data.guidance\n : 'Reactivate via POST /api/endpoints/{id}/reactivate, or update the URL on the calling agent.',\n },\n ],\n },\n };\n }\n\n if (!response.ok) {\n // 4.0.0: a 401 from verify-access can ONLY mean the backend rejected\n // THIS INTEGRATION'S own API key — agent denials come back as 200 with\n // access.allowed=false. Surface it as merchant-misconfig, not a\n // transient api_error: it is deterministic until the operator rotates\n // the key, and the \"retry with backoff\" template actively misleads.\n // Backends ≥ this round also send code: api_key_* + remediation URLs.\n if (response.status === 401) {\n return {\n success: false,\n error: data.message || data.error || 'Integration API key rejected',\n misconfig: true,\n misconfigCode: typeof data?.code === 'string' ? data.code : undefined,\n remediation:\n data?.remediation && typeof data.remediation === 'object'\n ? (data.remediation as { docsUrl?: string; manageUrl?: string })\n : undefined,\n correlationId: typeof data?.correlationId === 'string' ? data.correlationId : undefined,\n attemptId: typeof data?.attemptId === 'string' ? data.attemptId : undefined,\n };\n }\n // Round-10 (#47, O5): propagate correlationId on the error path too if\n // the server happened to include one (some 5xx error envelopes carry\n // it). Lets the SDK pass it through to the adapter onDenied handler.\n return {\n success: false,\n error: data.message || data.error || `API returned ${response.status}`,\n correlationId: typeof data?.correlationId === 'string' ? data.correlationId : undefined,\n attemptId: typeof data?.attemptId === 'string' ? data.attemptId : undefined,\n };\n }\n\n return data;\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n return {\n success: false,\n error: `Failed to call verify-access API: ${message}`,\n };\n }\n}\n\n/**\n * Options for a single verify() call (as opposed to GatewayConfig, which is\n * per-gateway). Mirrors the Java SDK's `verify(request, bypassCache)`.\n */\nexport interface VerifyOptions {\n /**\n * Skip the cache READ for this call so the request is guaranteed to reach\n * the backend (the result is still written to the cache if cacheable).\n * Used by the step-up hold-and-poll retry: the re-verify after an approval\n * must hit the server to redeem the single-use approval.\n */\n bypassCache?: boolean;\n}\n\n/**\n * Main verification function\n */\nexport async function verify(\n config: GatewayConfig,\n request: VerificationRequest,\n options?: VerifyOptions\n): Promise<VerificationResult> {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n\n // v2.5.0: read well-known URLs from cache (prefetched by adapter at middleware creation).\n // Synchronous — never triggers a fetch. If cache is empty, guidance URLs are empty strings.\n const urls = mergedConfig.apiBaseUrl\n ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl)\n : undefined;\n\n // One-time init self-test — fire-and-forget by default, never blocks verify().\n // Audit F-A6-33: when GatewayConfig.strictInit is set, the init promise is\n // awaited and any failure throws synchronously so callers learn about\n // misconfigured apiBaseUrl on the first verify() call rather than silently\n // running with broken auth.\n if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {\n if (mergedConfig.strictInit) {\n await performInitCheck(\n mergedConfig.apiBaseUrl,\n mergedConfig.debug,\n true,\n mergedConfig.apiKey\n );\n } else {\n void performInitCheck(\n mergedConfig.apiBaseUrl,\n mergedConfig.debug,\n false,\n mergedConfig.apiKey\n );\n }\n }\n\n // Deprecation warning for v2.3.0 removed config fields. Fires once per process.\n if (\n !deprecationWarningShown &&\n (config.minTrustScore !== undefined || config.minTrustScoreForFull !== undefined)\n ) {\n deprecationWarningShown = true;\n console.warn(\n '[VerificationGateway] minTrustScore / minTrustScoreForFull are deprecated in v2.3.0 ' +\n 'and have no effect. Server is now the single source of truth for access decisions ' +\n '(verified ID + runtime challenge + PDLSS + trust score). To gate access ' +\n \"to an endpoint, configure the endpoint's trust_score_requirement server-side.\"\n );\n }\n\n // v2.3.0: anonymous traffic no longer short-circuits here. We forward the\n // request to the server with no agentId; the server applies the endpoint's\n // unverifiedAgentPolicy and returns advisory. createGuidanceResponse remains\n // as the offline fallback if the API itself fails (handled below).\n\n // Check cache first. Caching on unless caller explicitly sets cacheTtl: 0\n // to disable. Undefined falls through to the 60s default at cacheResult\n // write time. options.bypassCache skips the read only (hold-and-poll retry\n // must reach the backend to redeem its approval).\n if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache) {\n const cached = getCachedResult(request, mergedConfig.counterpartyId);\n if (cached) {\n if (mergedConfig.debug) {\n console.log('[VerificationGateway] Returning cached result');\n }\n // 3.12.0 (Trust Record, §33.5 #3): attemptId is excluded from the cache\n // key, so the stored result may carry a PRIOR attempt's id. Overwrite it\n // with this request's own attemptId — or drop it when this request has\n // none — a cached verdict must never leak another attempt's handle.\n // Shallow clone: never mutate the stored entry (a concurrent holder of\n // the same cached object may still be reading it).\n const hit: VerificationResult = { ...cached };\n if (request.attemptId) {\n hit.attemptId = request.attemptId;\n } else {\n delete hit.attemptId;\n }\n return hit;\n }\n }\n\n // Inject counterparty info from config if not already set in request\n const enrichedRequest = { ...request };\n if (!enrichedRequest.counterpartyUrl && mergedConfig.counterpartyUrl) {\n enrichedRequest.counterpartyUrl = mergedConfig.counterpartyUrl;\n }\n if (!enrichedRequest.counterpartyType && mergedConfig.counterpartyType) {\n enrichedRequest.counterpartyType = mergedConfig.counterpartyType;\n }\n\n // Call the API\n if (mergedConfig.debug) {\n console.log('[VerificationGateway] Calling verify-access API');\n }\n\n const apiResponse = await callVerifyAccessAPI(mergedConfig, enrichedRequest);\n\n // 3.6.0: the server emits a top-level denialReasons array on every deny\n // path (same strings as the failure messages). Non-empty → authoritative;\n // absent/empty → fall back to the client-side reconstructions below.\n const rawServerDenialReasons = (apiResponse as Record<string, unknown>).denialReasons;\n const serverDenialReasons =\n Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0\n ? (rawServerDenialReasons as string[])\n : undefined;\n\n // Handle API errors\n if (!apiResponse.success) {\n // Round-10 (#47, O5): distinguish API errors from missing-credentials.\n // The previous default tagged any failure with the \"register your\n // agent\" template, which is misleading to a verified partner whose\n // verify-access call hit a 500. The `api_error` source surfaces a\n // typed `verify_access.api_error` failure entry instead.\n // 4.0.0: a rejected merchant key gets its own source — deterministic\n // misconfig must not wear the \"temporarily unavailable, retry\" template.\n return createGuidanceResponse(mergedConfig, apiResponse.error, {\n source: apiResponse.misconfig ? 'merchant_misconfig' : 'api_error',\n correlationId: (apiResponse as { correlationId?: string }).correlationId,\n attemptId: (apiResponse as { attemptId?: string }).attemptId,\n remediation: apiResponse.remediation,\n urls,\n });\n }\n\n // Check access result\n if (!apiResponse.access?.allowed) {\n // v2.9.8 (defect M1): aggregated failures across every gate that\n // denied. Surface them on the result so the integrator can see every\n // blocker in one go instead of the previous fail-fast cascade.\n const aggregatedFailures = (apiResponse.access as Record<string, unknown> | undefined)\n ?.failures as Array<{ dimension: string; message: string; guidance?: string }> | undefined;\n // Round-18 G4: backend denied access (PDLSS or other gate); identity status\n // depends on whether the backend resolved the caller. Read from\n // verificationContext.idVerified; default false if absent (anonymous or\n // identity-fail paths land here too). policyAllowed is false by definition\n // in this branch (apiResponse.access.allowed === false).\n const idVerifiedFromBackend =\n (apiResponse.verificationContext as { idVerified?: boolean } | undefined)?.idVerified ===\n true;\n const result: EnhancedVerificationResult = {\n identityVerified: idVerifiedFromBackend,\n policyAllowed: false,\n // 3.6.0: the server now emits top-level denialReasons on every deny\n // path — prefer it verbatim; the failure-message reconstruction remains\n // as the fallback for older backends.\n denialReasons:\n serverDenialReasons ??\n (aggregatedFailures && aggregatedFailures.length > 0\n ? aggregatedFailures.map((f) => f.message)\n : apiResponse.access?.reason\n ? [apiResponse.access.reason]\n : ['Access denied']),\n failures: aggregatedFailures,\n requiresStepUp: apiResponse.access?.requiresStepUp,\n requiresApproval: apiResponse.access?.requiresApproval,\n guidance: {\n message: apiResponse.access?.reason || 'Access denied by PDLSS policy',\n registrationUrl: urls?.registrationUrl ?? '',\n documentationUrl: urls?.documentationUrl ?? '',\n // 5.1.0: anonymous callers land on this branch — steer MCP-hosted\n // agents to the connector (this is what the edge deny body surfaces).\n ...(mcpGuidance(urls) && { mcp: mcpGuidance(urls) }),\n },\n verifiedAt: new Date(),\n // Extract sessionId so decisions can be recorded for denials too\n sessionId: (apiResponse as Record<string, unknown>).sessionId as string | undefined,\n // Anonymous traffic has no session → correlationId is the per-attempt\n // linking key (the sessionId-equivalent for anonymous callers).\n correlationId: (apiResponse as Record<string, unknown>).correlationId as string | undefined,\n // 3.12.0 (Trust Record): server echoes the attempt-chain handle on\n // every branch — denials included (the funnel outlives one deny).\n attemptId: (apiResponse as Record<string, unknown>).attemptId as string | undefined,\n recommendation: (apiResponse as Record<string, unknown>)\n .recommendation as EnhancedVerificationResult['recommendation'],\n recommendationReasons: (apiResponse as Record<string, unknown>).recommendationReasons as\n | string[]\n | undefined,\n stepUpApproval: (apiResponse as Record<string, unknown>).stepUpApproval as\n | StepUpApprovalInfo\n | undefined,\n settlement: (apiResponse as Record<string, unknown>).settlement as\n | SettlementArtifact\n | undefined,\n settlementOutcome: (apiResponse as Record<string, unknown>).settlementOutcome as\n | SettlementOutcomeInfo\n | undefined,\n };\n\n return result;\n }\n\n // Build successful result\n const agent: VerifiedAgent | undefined = apiResponse.agent\n ? {\n astraId: apiResponse.agent.astraId,\n name: apiResponse.agent.name,\n trustScore: apiResponse.agent.trustScore,\n trustLevel: getTrustLevel(apiResponse.agent.trustScore),\n blockchainVerified: apiResponse.agent.blockchainStatus === 'verified',\n status: apiResponse.agent.agentStatus as VerifiedAgent['status'],\n }\n : undefined;\n\n const developer: VerifiedDeveloper | undefined = apiResponse.developer\n ? {\n astradId: apiResponse.developer.kyaOwnerId,\n name: apiResponse.developer.fullName,\n trustScore: apiResponse.developer.trustScore || 0,\n verified: apiResponse.developer.identityVerified,\n }\n : undefined;\n\n const organization: VerifiedOrganization | undefined = apiResponse.organization\n ? {\n name: apiResponse.organization.name,\n verified: apiResponse.organization.verified,\n trustScore: apiResponse.organization.trustScore,\n }\n : undefined;\n\n // Verification context — structured \"why\" the merchant gets in v2.2.4+.\n // Carries appliedPolicy (no UUIDs), pdlssCheck summary, dynamic trust score,\n // and policy-driven attestations. Replaces the old over-sharing `pdlss` block.\n const verificationContext = apiResponse.verificationContext;\n\n const result: EnhancedVerificationResult = {\n // 4.5.0 (probe F1): read idVerified from verificationContext with the\n // SAME absent⇒false default as the deny branch. The previous `!== false`\n // default manufactured `identityVerified: true` for anonymous\n // allow_full/allow_partial/audit responses (which allow access but omit\n // verificationContext entirely) — the \"anonymous caller labelled\n // verified\" defect. Every authenticated success response has carried\n // `verificationContext.idVerified: true` since v2.2.4, so a truth claim\n // this load-bearing must never be defaulted on.\n identityVerified:\n (apiResponse.verificationContext as { idVerified?: boolean } | undefined)?.idVerified ===\n true,\n policyAllowed: true,\n agent,\n developer,\n organization,\n appliedPolicy: apiResponse.access?.appliedPolicy,\n verificationContext,\n requiresStepUp: apiResponse.access?.requiresStepUp,\n requiresApproval: apiResponse.access?.requiresApproval,\n verifiedAt: new Date(),\n cacheTtl: mergedConfig.cacheTtl,\n // Handshake Protocol v10 enhanced fields (present when backend returns them)\n sessionId: (apiResponse as Record<string, unknown>).sessionId as string | undefined,\n // v2.3.10 (defect #34, round-4): anonymous responses surface correlationId\n // (no session row exists for unverified callers).\n correlationId: (apiResponse as Record<string, unknown>).correlationId as string | undefined,\n // 3.12.0 (Trust Record): server-echoed attempt-chain handle.\n attemptId: (apiResponse as Record<string, unknown>).attemptId as string | undefined,\n runtimeChallenge: (apiResponse as Record<string, unknown>).runtimeChallenge as\n | RuntimeChallengeResult\n | undefined,\n tokenGuidance: (apiResponse as Record<string, unknown>).tokenGuidance as\n | TokenGuidance\n | undefined,\n recommendation: (apiResponse as Record<string, unknown>)\n .recommendation as EnhancedVerificationResult['recommendation'],\n recommendationReasons: (apiResponse as Record<string, unknown>).recommendationReasons as\n | string[]\n | undefined,\n warningHeader: (apiResponse as Record<string, unknown>).warningHeader as\n | { name: string; value: string }\n | undefined,\n stepUpApproval: (apiResponse as Record<string, unknown>).stepUpApproval as\n | StepUpApprovalInfo\n | undefined,\n settlement: (apiResponse as Record<string, unknown>).settlement as\n | SettlementArtifact\n | undefined,\n settlementOutcome: (apiResponse as Record<string, unknown>).settlementOutcome as\n | SettlementOutcomeInfo\n | undefined,\n };\n\n // Enforce AstraSync recommendation\n if (result.recommendation === 'deny') {\n // Round-18 G4: recommendation-driven deny lands on the success-path\n // construction (identity was resolved). Flip policy only — identity stays\n // verified — so adapters return 403 (re-auth won't help; the policy\n // decision is the blocker).\n result.policyAllowed = false;\n // 3.6.0: server-sent top-level denialReasons wins; recommendationReasons\n // reconstruction is the fallback for older backends.\n result.denialReasons = serverDenialReasons ??\n result.recommendationReasons ?? ['Access denied by AstraSync recommendation'];\n result.guidance = result.runtimeChallenge\n ? {\n message: `Verification failed: ${result.runtimeChallenge.reason || 'runtime challenge failed'}`,\n registrationUrl: urls?.registrationUrl ?? '',\n documentationUrl: urls?.documentationUrl ?? '',\n }\n : {\n message: result.recommendationReasons?.[0] || 'Access denied by AstraSync recommendation',\n registrationUrl: urls?.registrationUrl ?? '',\n documentationUrl: urls?.documentationUrl ?? '',\n };\n } else if (result.recommendation === 'step_up_required') {\n // The escalation signal rides on `requiresStepUp` / `stepUpApproval`, not\n // on any masquerade of the (now-removed) access band.\n result.requiresStepUp = true;\n result.denialReasons = serverDenialReasons ??\n result.recommendationReasons ?? ['Step-up verification required'];\n }\n\n // Cache the result. Never cache denials (agent may fix challenge endpoint\n // and retry) NOR step-up escalations (3.6.0 — the same bug the Java SDK\n // had: a cached step_up_required verdict keeps denying retries the owner\n // has already approved, and its pollUrl goes stale once consumed/expired).\n // Escalations stay uncached even when the caller pins an explicit cacheTtl.\n const escalates =\n result.requiresStepUp === true ||\n result.requiresApproval === true ||\n result.recommendation === 'step_up_required';\n if (mergedConfig.cacheTtl !== 0 && result.recommendation !== 'deny' && !escalates) {\n cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);\n }\n\n return result;\n}\n\n/**\n * Record a counterparty's grant/deny decision for a verification session.\n * Fire-and-forget — errors are silently swallowed.\n */\nexport async function recordDecision(\n config: GatewayConfig,\n sessionId: string,\n decision: 'granted' | 'denied',\n reason?: string\n): Promise<void> {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n headers['X-API-Key'] = config.apiKey;\n }\n\n await sdkFetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ decision, reason }),\n }).catch(() => {\n /* fire-and-forget */\n });\n}\n\n/**\n * Report post-hoc consideration/outcome data for an attempt chain (Trust\n * Record). Use when the signal never reached verify-access: a\n * catalog served from a local cache, a transport-side deny (sku not found,\n * over budget), or a terminal outcome (settled / blocked on step-up).\n * Authenticated with the same credentials as verify(); the backend responds\n * 202 and folds the data into the attempt chain keyed by `attemptId`.\n *\n * Fire-and-forget — errors are silently swallowed, never throws.\n */\nexport async function reportAttempt(config: GatewayConfig, report: AttemptReport): Promise<void> {\n const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl!;\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n headers['X-API-Key'] = config.apiKey;\n }\n\n // counterpartyId resolution mirrors reportUnregisteredAttempt: an explicit\n // report value wins; otherwise attribute to the configured endpoint.\n const body = {\n ...report,\n ...(!report.counterpartyId &&\n config.counterpartyId && { counterpartyId: config.counterpartyId }),\n };\n\n await sdkFetch(`${apiBaseUrl}/agents/verify-access/attempt-report`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n }).catch(() => {\n /* fire-and-forget */\n });\n}\n\n/**\n * Fetch the per-route policy for an endpoint from the AstraSync backend.\n * v2.9.7 moved policy authority into the dashboard — the SDK no longer\n * accepts `routes` from merchant-side source code, it fetches them from\n * here on init (and refreshes periodically).\n *\n * Returns `null` when the request fails for any reason — the caller decides\n * how to fall back (the middleware allows-all when no policy is loaded so\n * a misconfigured init doesn't take down the merchant's API).\n */\nexport async function fetchRoutes(\n config: GatewayConfig,\n counterpartyId: string\n): Promise<RouteAccessConfigShape[] | null> {\n if (!counterpartyId) return null;\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n headers['X-API-Key'] = config.apiKey;\n }\n try {\n const response = await sdkFetch(\n `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes`,\n { method: 'GET', headers }\n );\n if (!response.ok) return null;\n const body = (await response.json()) as { data?: { routes?: RouteAccessConfigShape[] } };\n return body.data?.routes ?? [];\n } catch {\n return null;\n }\n}\n\n/**\n * Minimal shape of an EndpointRoute as the SDK consumes it. Mirrors the\n * server's `EndpointRoute` type and the SDK's `RouteAccessConfig` — same\n * JSON moves between server and SDK unchanged.\n */\nexport interface RouteAccessConfigShape {\n pattern: string;\n method: string;\n /** Pass through unenforced (observe). Replaces `minAccessLevel: 'none'`. */\n observe?: boolean;\n minTrustScore?: number;\n requiredPurposes?: string[];\n allowedPurposes?: string[];\n allowedJurisdictions?: string[];\n maxDuration?: number;\n maxTransactionValue?: number;\n requirePurpose?: boolean;\n /** Send-mapping — see RouteAccessConfig in types.ts. */\n purpose?: string;\n action?: string;\n}\n\n/**\n * Verify an agent AND automatically record the grant/deny decision.\n *\n * This is the recommended entry point for counterparties that call verify()\n * directly (e.g. MCP servers) rather than using createMiddleware().\n * It adds createSession: true, then fire-and-forgets the decision.\n */\nexport async function verifyAndRecord(\n config: GatewayConfig,\n request: VerificationRequest,\n options?: VerifyOptions\n): Promise<VerificationResult> {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n const result = await verify(mergedConfig, { ...request, createSession: true }, options);\n const sessionId = (result as EnhancedVerificationResult).sessionId;\n\n if (sessionId) {\n // Round-18 G4: a session is \"granted\" only if identity verified AND\n // policy allowed; either failing is a deny.\n if (result.identityVerified && result.policyAllowed) {\n recordDecision(mergedConfig, sessionId, 'granted').catch(() => {});\n } else {\n recordDecision(mergedConfig, sessionId, 'denied', result.denialReasons?.[0]).catch(() => {});\n }\n }\n\n return result;\n}\n\n/**\n * Report an unregistered agent attempt (no AstraSync credentials).\n * Called by SDK adapters when an agent is redirected to /docs/agent-access.\n * Fire-and-forget — errors are silently swallowed.\n */\nexport async function reportUnregisteredAttempt(\n config: GatewayConfig,\n data: {\n /**\n * 3.6.0: optional — the backend attributes by counterpartyId first (sent\n * from config below) and requires at least ONE of counterpartyId/URL.\n * A null/undefined URL is omitted from the body, not sent as null.\n */\n counterpartyUrl?: string;\n counterpartyType?: string;\n sourceIp?: string;\n userAgent?: string;\n requestPath?: string;\n requestMethod?: string;\n /**\n * 3.9.0 — edge-adapter evidence fields, persisted into the\n * event's eventData by the backend: `protocol` = detected transport/\n * credential protocol; `evidence` = light crypto-free detection detail.\n */\n protocol?: string;\n evidence?: Record<string, unknown>;\n /**\n * Maximal metadata capture — the full sanitised metadata for an\n * unregistered caller (every signal legitimately visible on the wire).\n * Persisted into eventData; feeds platform attribution and (via\n * correlationId) registration auto-fill. Fire-and-forget, so free.\n */\n observedMetadata?: ObservedMetadata;\n }\n): Promise<void> {\n const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl!;\n\n const { counterpartyUrl, ...rest } = data;\n const body = {\n ...rest,\n ...(counterpartyUrl && { counterpartyUrl }),\n ...(config.counterpartyId && { counterpartyId: config.counterpartyId }),\n };\n\n await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n }).catch(() => {\n /* fire-and-forget */\n });\n}\n\n/**\n * Report a counterparty-side PDLSS pre-check failure.\n * Called by SDK adapters when the agent's requested PDLSS exceeds\n * counterparty-defined maximums BEFORE calling verify-access.\n * Fire-and-forget — errors are silently swallowed.\n */\nexport async function reportCounterpartyPreCheckFailure(\n config: GatewayConfig,\n data: {\n agentId: string;\n counterpartyUrl: string;\n counterpartyType?: string;\n failures: Array<{\n field: string;\n requested: string | number;\n limit: string | number | string[];\n message: string;\n }>;\n requestPath?: string;\n requestMethod?: string;\n }\n): Promise<void> {\n const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl!;\n\n await sdkFetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(data),\n }).catch(() => {\n /* fire-and-forget */\n });\n}\n\n/**\n * Quick verification — checks credentials and policy in one call.\n *\n * The return shape mirrors `VerificationResult`'s split — partners\n * writing custom handlers around `quickVerify` get the same identity/policy\n * distinction as those calling `verify()` directly. Map to HTTP status the\n * same way: `!identityVerified` → 401; `identityVerified && !policyAllowed`\n * → 403.\n */\nexport async function quickVerify(\n config: GatewayConfig,\n credentials: AgentCredentials\n): Promise<{\n identityVerified: boolean;\n policyAllowed: boolean;\n reason?: string;\n}> {\n const result = await verify(config, {\n credentials,\n purpose: 'verification',\n });\n\n return {\n identityVerified: result.identityVerified,\n policyAllowed: result.policyAllowed,\n reason: result.denialReasons?.[0],\n };\n}\n","/**\n * Maximal metadata capture — the shared, edge-safe sanitiser.\n *\n * AstraSync's thesis is total metadata capture: every header and connection\n * signal an inbound agent presents is a signal we want to keep. This module is\n * the ONE place that decides what is safe to store verbatim, what is a secret\n * to drop, and what credential headers reduce to a safe *format prefix* (a\n * platform signal, never the secret itself).\n *\n * It runs in three places that must agree byte-for-byte:\n * - the edge adapter (`@astrasyncai/adapter-lambda`) — the richest capture\n * point (full CloudFront header arrays),\n * - the SDK adapters (express / nextjs) — for SDK-only merchants with no edge,\n * - (indirectly) the backend, which stores whatever the above forward.\n *\n * Pure — no Node built-ins, no I/O — so it is safe in Lambda@Edge, the browser\n * build, and Deno. Capture is cheap: at the edge and SDK it is local string\n * work, and the backend stores it on a fire-and-forget event insert, so it adds\n * ZERO verify-access decision latency. Detection (which few signals imply which\n * vendor) is a separate, curated concern — see `platform-signatures.ts`.\n */\n\n/**\n * Version of the capture semantics (what is kept, dropped, reduced, or\n * derived — and under which key names).\n *\n * Capture semantics are FROZEN within a package major version: a given\n * `schemaVersion` always means the same field vocabulary and the same\n * keep/drop/reduce rules. The number bumps only on a SEMANTIC change to\n * capture (a renamed key, a changed reduction rule) — never for additive\n * signals, and never merely because the package version moved. To tell\n * builds apart, use the `sdkVersion` already present on the verify body;\n * `schemaVersion` answers the different question \"how do I interpret this\n * stored metadata?\".\n */\nexport const CAPTURE_SCHEMA_VERSION = 1;\n\n/**\n * The captured, sanitised view of an inbound agent's request metadata. Attached\n * to verify-access / beacon payloads as `callerMetadata.observedMetadata`.\n */\nexport interface ObservedMetadata {\n /** Header name → value, secrets removed, verbatim otherwise. */\n headers: Record<string, string>;\n /**\n * Safe key-format prefix of a credential header (e.g. `sk-ant-api03`,\n * `sk-proj`, `AIza`) — a platform signal, NEVER the secret. Absent if no\n * credential header was present or none matched a known format.\n */\n apiKeyFormat?: string;\n /**\n * The subset of `headers` that are known platform-signal headers\n * (`openai-organization`, `x-goog-user-project`, `anthropic-version`, …),\n * pulled out for convenient detection + display. Values are the same\n * sanitised strings as in `headers`.\n */\n platformHeaders?: Record<string, string>;\n /**\n * Connection-layer signals (IP, ASN, country, TLS version, HTTP version,\n * device class, TLS fingerprint). Edge adapters populate this from the\n * platform's native connection surface — the authoritative source. SDK\n * adapters populate it as a fallback via {@link deriveConnectionFromHeaders}\n * when a CDN in front of the merchant injected the equivalent headers;\n * absent when neither source had anything.\n */\n connection?: Record<string, string>;\n /**\n * Number of cookie pairs the caller sent. The `cookie` header VALUE is a\n * secret and is always dropped; the COUNT is a client-shape signal\n * (browsers carry cookies, most automation carries none) that would\n * otherwise be unobservable downstream. Absent when no cookie header\n * arrived.\n */\n cookieCount?: number;\n /**\n * Query-string parameter NAMES (deduped, lowercased) — values are NEVER\n * captured (they routinely carry OAuth codes, tokens, and PII, and no\n * deny-list is complete). Names alone fingerprint client shape. Populated\n * by the edge path, which sees the raw query string.\n */\n queryKeys?: string[];\n /**\n * Version of the capture semantics this object was produced under — see\n * {@link CAPTURE_SCHEMA_VERSION}. Optional for backward compatibility with\n * metadata captured before the field existed.\n */\n schemaVersion?: number;\n}\n\n/**\n * Result of sanitising a raw header map. `headers` is always present (possibly\n * empty); `apiKeyFormat` / `platformHeaders` only when a signal was found.\n */\nexport interface SanitizeHeadersResult {\n headers: Record<string, string>;\n apiKeyFormat?: string;\n platformHeaders?: Record<string, string>;\n /** Cookie-pair count; the cookie value itself is always dropped. */\n cookieCount?: number;\n}\n\n/**\n * Header names dropped entirely — genuine secrets or our own credentials that\n * describe the request's transport, not the agent. Matched case-insensitively\n * on the exact header name.\n */\nconst SECRET_HEADER_NAMES = new Set([\n 'cookie',\n 'set-cookie',\n 'x-csrf-token',\n 'proxy-authorization',\n 'x-astrasync-signature',\n 'x-astrasync-secret',\n 'x-hub-signature',\n 'x-hub-signature-256',\n // Short-lived verify dedupe token — must never persist in stored metadata.\n 'x-astra-verified-hop',\n]);\n\n/**\n * Header-name prefixes dropped entirely — our own credential / signing surface\n * (`x-astrasync-*`, `kya-*`) must never round-trip into stored metadata.\n */\nconst SECRET_HEADER_PREFIXES = ['x-astrasync-', 'kya-', 'kya_'];\n\n/**\n * Credential headers whose VALUE is a secret but whose FORMAT is a signal. We\n * keep the leading format prefix (up to the random tail) and drop the rest.\n */\nconst CREDENTIAL_HEADER_NAMES = new Set([\n 'authorization',\n 'x-api-key',\n 'x-goog-api-key',\n 'api-key',\n]);\n\n/**\n * Header names that carry a raw signature value (drop the value; the presence\n * is not worth a bloat risk). Matched as a suffix so `x-*-signature` variants\n * are covered without an exhaustive list.\n */\nfunction isSignatureHeader(name: string): boolean {\n return name === 'signature' || name.endsWith('-signature') || name.endsWith('-signature-256');\n}\n\n/**\n * Known platform-signal headers — pulled into `platformHeaders` for convenient\n * detection + display. Exact names plus a few prefixes (`x-goog-`,\n * `x-stainless-` — the OpenAI/Anthropic SDK client fingerprint headers).\n */\nconst PLATFORM_HEADER_NAMES = new Set([\n 'openai-organization',\n 'openai-project',\n 'openai-version',\n 'openai-processing-ms',\n 'anthropic-version',\n 'anthropic-beta',\n 'x-goog-user-project',\n 'x-goog-api-client',\n 'user-agent',\n 'x-agent-card-url',\n 'x-agent-provider',\n]);\n// `sec-ch-*` client hints are platform signals: Chromium forks (agentic\n// browsers included) often carry their real brand in `sec-ch-ua` even when\n// the UA string mimics stock Chrome.\nconst PLATFORM_HEADER_PREFIXES = [\n 'x-goog-',\n 'x-stainless-',\n 'x-anthropic-',\n 'x-openai-',\n 'sec-ch-',\n];\n\n/**\n * Known API-key format prefixes, longest-first so `sk-ant-api03` wins over\n * `sk-ant` and `sk-`. Each is the SAFE, non-secret leading token of a\n * credential; the random tail is never captured.\n */\nconst KEY_FORMAT_PREFIXES = [\n 'sk-ant-api03',\n 'sk-ant-api',\n 'sk-ant',\n 'sk-proj',\n 'sk-svcacct',\n 'sk-or-v1',\n 'sk-or',\n 'sk-lf',\n 'sk-',\n 'gsk_',\n 'aiza',\n 'ya29',\n 'ghp_',\n 'gho_',\n 'github_pat_',\n 'xai-',\n 'pplx-',\n 'r8_',\n 'hf_',\n 'astra-',\n 'astrae-',\n];\n\n/** Size caps — the only guard against jsonb bloat from a hostile POST. */\nexport const MAX_HEADERS = 64;\nexport const MAX_HEADER_VALUE_BYTES = 1024;\nexport const MAX_TOTAL_BYTES = 16 * 1024;\n\nfunction byteLength(s: string): number {\n // Edge-safe (no Buffer): count UTF-8 bytes without allocating a Buffer.\n let bytes = 0;\n for (let i = 0; i < s.length; i++) {\n const code = s.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff) {\n bytes += 4;\n i++; // surrogate pair\n } else bytes += 3;\n }\n return bytes;\n}\n\n/**\n * Extract the safe key-format prefix from a credential header value. Strips a\n * leading `Bearer ` / `Basic ` scheme, then returns the longest known prefix\n * that the (lowercased) value starts with. Returns undefined for opaque values\n * (e.g. a bare UUID token) so we never guess a format we don't recognise.\n */\nexport function extractApiKeyFormat(rawValue: string): string | undefined {\n if (!rawValue) return undefined;\n let value = rawValue.trim();\n const spaceIdx = value.indexOf(' ');\n if (spaceIdx > 0) {\n const scheme = value.slice(0, spaceIdx).toLowerCase();\n if (scheme === 'bearer' || scheme === 'basic' || scheme === 'token') {\n value = value.slice(spaceIdx + 1).trim();\n }\n }\n const lower = value.toLowerCase();\n for (const prefix of KEY_FORMAT_PREFIXES) {\n if (lower.startsWith(prefix)) {\n // Return the prefix as-declared in the list (canonical casing).\n return prefix;\n }\n }\n return undefined;\n}\n\nfunction isSecretHeader(name: string): boolean {\n if (SECRET_HEADER_NAMES.has(name)) return true;\n if (SECRET_HEADER_PREFIXES.some((p) => name.startsWith(p))) return true;\n if (isSignatureHeader(name)) return true;\n return false;\n}\n\nfunction isPlatformHeader(name: string): boolean {\n if (PLATFORM_HEADER_NAMES.has(name)) return true;\n return PLATFORM_HEADER_PREFIXES.some((p) => name.startsWith(p));\n}\n\n/**\n * Sanitise a raw header map for storage. Deny-lists genuine secrets, reduces\n * credential headers to a safe format prefix, keeps everything else verbatim,\n * and enforces the size caps that bound jsonb growth.\n *\n * Accepts any string→(string|string[]|undefined) map (Node's `req.headers`,\n * CloudFront's flattened headers, a plain object). Multi-value headers are\n * joined with `, ` per RFC 7230.\n */\nexport function sanitizeHeaders(\n raw: Record<string, string | string[] | undefined> | undefined | null\n): SanitizeHeadersResult {\n const headers: Record<string, string> = {};\n const platformHeaders: Record<string, string> = {};\n let apiKeyFormat: string | undefined;\n let cookieCount: number | undefined;\n let totalBytes = 0;\n let count = 0;\n\n if (!raw) return { headers };\n\n for (const rawName of Object.keys(raw)) {\n if (count >= MAX_HEADERS) break;\n const name = rawName.toLowerCase();\n const rawValue = raw[rawName];\n if (rawValue === undefined) continue;\n const joined = Array.isArray(rawValue) ? rawValue.join(', ') : String(rawValue);\n\n // Credential headers: capture the format prefix, drop the secret.\n if (CREDENTIAL_HEADER_NAMES.has(name)) {\n const fmt = extractApiKeyFormat(joined);\n if (fmt && !apiKeyFormat) apiKeyFormat = fmt;\n continue;\n }\n\n // Cookie: the value is a secret (dropped below), but the PAIR COUNT is a\n // client-shape signal worth keeping.\n if (name === 'cookie') {\n cookieCount = joined.split(';').filter((s) => s.trim().length > 0).length;\n }\n\n // Genuine secrets: drop entirely.\n if (isSecretHeader(name)) continue;\n\n // Everything else describing the agent: keep, bounded.\n let value = joined;\n if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {\n value = value.slice(0, MAX_HEADER_VALUE_BYTES);\n }\n const entryBytes = byteLength(name) + byteLength(value);\n if (totalBytes + entryBytes > MAX_TOTAL_BYTES) continue;\n totalBytes += entryBytes;\n count++;\n headers[name] = value;\n if (isPlatformHeader(name)) platformHeaders[name] = value;\n }\n\n const result: SanitizeHeadersResult = { headers };\n if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;\n if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;\n if (cookieCount !== undefined) result.cookieCount = cookieCount;\n return result;\n}\n\n/**\n * Pull the known platform-signal headers out of an ALREADY-sanitised header\n * map. `sanitizeHeaders` already computes this as a by-product; this standalone\n * form is exported for call-sites (edge, tests) that hold a sanitised map and\n * want only the platform subset. Never re-run over raw (unsanitised) headers.\n */\nexport function extractPlatformHeaders(\n sanitized: Record<string, string> | undefined | null\n): Record<string, string> {\n const out: Record<string, string> = {};\n if (!sanitized) return out;\n for (const name of Object.keys(sanitized)) {\n if (isPlatformHeader(name.toLowerCase())) out[name.toLowerCase()] = sanitized[name];\n }\n return out;\n}\n\n/**\n * Precedence-ordered CDN header sources for each derived `connection` key.\n * First present header wins; the special `x-forwarded-for` source takes the\n * LEFTMOST (client) entry of the comma-separated chain.\n */\nconst CONNECTION_HEADER_SOURCES: ReadonlyArray<readonly [key: string, headers: readonly string[]]> =\n [\n ['ip', ['cf-connecting-ip', 'fastly-client-ip', 'true-client-ip', 'x-real-ip']],\n [\n 'country',\n ['cf-ipcountry', 'x-vercel-ip-country', 'cloudfront-viewer-country', 'fastly-geo-country'],\n ],\n ['region', ['x-vercel-ip-country-region', 'cloudfront-viewer-country-region']],\n ['countryRegionName', ['cloudfront-viewer-country-region-name']],\n ['city', ['x-vercel-ip-city', 'cloudfront-viewer-city']],\n ['latitude', ['x-vercel-ip-latitude', 'cloudfront-viewer-latitude']],\n ['longitude', ['x-vercel-ip-longitude', 'cloudfront-viewer-longitude']],\n ['postalCode', ['x-vercel-ip-postal-code', 'cloudfront-viewer-postal-code']],\n ['asn', ['cloudfront-viewer-asn']],\n ['tlsVersion', ['cloudfront-viewer-tls']],\n ['httpVersion', ['cloudfront-viewer-http-version']],\n ['timeZone', ['x-vercel-ip-timezone', 'cloudfront-viewer-time-zone']],\n ['metroCode', ['cloudfront-viewer-metro-code']],\n // TLS fingerprints can only be computed where TLS terminates. When a CDN\n // terminates TLS in front of the merchant, its fingerprint headers ARE the\n // connection-layer truth — normalising them here is the permanent design.\n // CloudFront delivers JA3/JA4 only via an origin request policy and only\n // for HTTPS viewer connections.\n ['tlsFingerprint', ['cf-ja3-hash', 'cloudfront-viewer-ja3-fingerprint']],\n ['ja4', ['cf-ja4', 'cloudfront-viewer-ja4-fingerprint']],\n // Header-structure fingerprint: browser/SDK-distinctive header ordering.\n ['headerOrder', ['cloudfront-viewer-header-order']],\n ['headerCount', ['cloudfront-viewer-header-count']],\n ];\n\n/**\n * Backend bound: observedMetadata.connection values longer than this fail\n * the platform's zod parse outright (boundedStringRecord(32, 256)), so every\n * emitter truncates — headerOrder routinely exceeds it.\n */\nexport const MAX_CONNECTION_VALUE_CHARS = 256;\n\n/**\n * Derive the connection-layer block from CDN-injected request headers.\n *\n * When a merchant's SDK-gated origin sits behind a CDN (Cloudflare, Fastly,\n * Vercel, CloudFront), the CDN has already seen the connection layer and\n * injected it as headers. This promotes those headers to the same\n * `connection` keys the edge adapters populate natively, so SDK-only\n * deployments still get IP / geo / TLS attribution. Precedence per key:\n *\n * - `ip`: `cf-connecting-ip` > `fastly-client-ip` > `true-client-ip` >\n * `x-real-ip` > `cloudfront-viewer-address` (port stripped) > leftmost\n * `x-forwarded-for`\n * - `country`: `cf-ipcountry` > `x-vercel-ip-country` >\n * `cloudfront-viewer-country` > `fastly-geo-country`\n * - `region` / `city` / `timeZone`: Vercel then CloudFront viewer headers\n * - `countryRegionName` / `asn` / `tlsVersion` / `httpVersion` /\n * `metroCode` / `headerOrder` / `headerCount`: `cloudfront-viewer-*`\n * - `tlsFingerprint`: `cf-ja3-hash` > `cloudfront-viewer-ja3-fingerprint`\n * - `ja4`: `cf-ja4` > `cloudfront-viewer-ja4-fingerprint`\n *\n * All keys are additive under `schemaVersion` 1 (the frozen-capture doctrine\n * bumps only for changed semantics, never for additive signals). Values are\n * truncated to MAX_CONNECTION_VALUE_CHARS — the platform's hard bound.\n *\n * Edge adapters keep their richer native collectors — native platform\n * signals are authoritative; this header derivation is the SDK-level\n * fallback. Header names are matched case-insensitively. Returns undefined\n * when no source header is present, so the `connection` key is simply\n * omitted (matching edge behaviour).\n */\nexport function deriveConnectionFromHeaders(\n headers: Record<string, string> | undefined | null\n): Record<string, string> | undefined {\n if (!headers) return undefined;\n const lower: Record<string, string> = {};\n for (const name of Object.keys(headers)) {\n const value = headers[name];\n if (typeof value === 'string' && value.length > 0) lower[name.toLowerCase()] = value;\n }\n\n const connection: Record<string, string> = {};\n for (const [key, sources] of CONNECTION_HEADER_SOURCES) {\n for (const source of sources) {\n const value = lower[source]?.trim();\n if (value) {\n connection[key] = value.slice(0, MAX_CONNECTION_VALUE_CHARS);\n break;\n }\n }\n }\n // ip fallback 1: cloudfront-viewer-address arrives as ip:port — strip the\n // trailing port only (IPv6-safe), unlike the dedicated client-ip headers.\n // The port itself is kept as its own key: ephemeral-port patterns signal\n // NAT/proxy connection reuse.\n {\n const addr = lower['cloudfront-viewer-address']?.trim();\n const portMatch = addr?.match(/:(\\d+)$/);\n if (portMatch) connection.sourcePort = portMatch[1];\n if (!connection.ip && addr) connection.ip = addr.replace(/:\\d+$/, '');\n }\n // ip fallback 2: leftmost (client) hop of the x-forwarded-for chain.\n if (!connection.ip) {\n const xff = lower['x-forwarded-for'];\n const client = xff?.split(',')[0]?.trim();\n if (client) connection.ip = client;\n }\n // tlsCipher: CloudFront folds the cipher into the viewer-tls string\n // (`TLSv1.3:TLS_AES_128_GCM_SHA256:fullHandshake`). `tlsVersion` stays\n // verbatim (reducing it would be a semantic change → schema v2); the\n // cipher is pulled out additively so it is queryable on its own.\n if (!connection.tlsCipher) {\n const cipher = connection.tlsVersion?.split(':')[1];\n if (cipher) connection.tlsCipher = cipher;\n }\n\n return Object.keys(connection).length > 0 ? connection : undefined;\n}\n\n/**\n * The FULL metadata capture for one request at an SDK adapter (express /\n * nextjs / mcp): sanitised headers, the safe credential format prefix, the\n * platform-signal subset, the CDN-derived connection block, and the capture\n * schema version. This is the ONE builder all SDK adapters share, so their\n * verify-access bodies agree byte-for-byte.\n *\n * Connection derivation runs over the RAW header map (pre-cap) — the size\n * caps guard storage growth and must never hide a geo/IP header that\n * happened to arrive late in an oversized map.\n */\nexport function buildSdkObservedMetadata(\n rawHeaders: Record<string, string | string[] | undefined> | undefined | null\n): ObservedMetadata {\n const { headers, apiKeyFormat, platformHeaders, cookieCount } = sanitizeHeaders(rawHeaders);\n const flat: Record<string, string> = {};\n if (rawHeaders) {\n for (const name of Object.keys(rawHeaders)) {\n const value = rawHeaders[name];\n if (value === undefined) continue;\n flat[name.toLowerCase()] = Array.isArray(value) ? value.join(', ') : String(value);\n }\n }\n const connection = deriveConnectionFromHeaders(flat);\n return {\n headers,\n ...(apiKeyFormat && { apiKeyFormat }),\n ...(platformHeaders && { platformHeaders }),\n ...(connection && { connection }),\n ...(cookieCount !== undefined && { cookieCount }),\n schemaVersion: CAPTURE_SCHEMA_VERSION,\n };\n}\n","/**\n * Direct-path step-up approval gate (post-3.1.0 F2 follow-up).\n *\n * The verify-access spending-Limit ladder returns an \"allowed-but-escalate\"\n * outcome for a transaction value in the human-in-the-loop band\n * `[autonomousThreshold, hardLimit)`: `policyAllowed: true` (it is NOT a deny)\n * plus `requiresStepUp` / `requiresApproval`. The bridge blocks these; the\n * direct-path adapters historically gated on `identityVerified && policyAllowed`\n * alone and would therefore ADMIT (and let the merchant settle) the very\n * purchase the bridge blocks - a cross-path bypass.\n *\n * The backend now creates a pending step-up approval record and returns a\n * `stepUpApproval` object with `{ approvalId, pollUrl, expiresAt }` in the\n * verify-access response. The owner can approve/deny from the dashboard\n * (Activity → Pending Verifications). Future channels: mobile push, browser\n * extension.\n *\n * Direct-path adapters (Express/MCP/Next.js) fail closed BY DEFAULT. The\n * `stepUpApproval` info is included in the deny response so integrations can\n * surface it (\"your agent owner needs to approve this\") or poll it themselves.\n * 3.6.0 adds an opt-in hold-and-poll (`stepUpMaxWaitMs` on GatewayConfig, see\n * ./stepup-poll.ts): the adapter holds the request, polls the approval, and on\n * 'approved' re-verifies once (cache-bypassed) so the backend redeems the\n * single-use approval — the MFA model the Java SDK ships default-on. Opt-in\n * here because a minor release must not silently convert instant 403s into\n * multi-minute holds (client timeouts, connection pools, edge wall-clocks).\n *\n * Detection keys on the granular Limit flags (`requiresStepUp` /\n * `requiresApproval`, set from the verify-access `access` block) rather than\n * the `recommendation` umbrella - both because those flags ARE what drive a\n * `step_up_required` recommendation server-side, and because it keeps the\n * unverified-agent-policy `audit` outcome (a deliberate allow-and-log) from\n * being caught here.\n */\nimport type { VerificationResult, StepUpApprovalInfo } from '../types';\n\nconst APPROVAL_REASON =\n 'Transaction is above the autonomous limit and requires human approval. The agent owner can approve or deny this transaction in the AstraSync dashboard.';\n\n/** True when the outcome needs human step-up/approval. */\nexport function requiresHumanApproval(result: VerificationResult): boolean {\n return result.requiresStepUp === true || result.requiresApproval === true;\n}\n\n/**\n * Annotate a result in place with the structured approval-required failure +\n * denial reason, so the adapters' existing deny renderers surface it like any\n * other failure. Mirrors the bridge's `commerce.intent.approval_required`.\n */\nexport function annotateApprovalRequired(result: VerificationResult): void {\n result.failures = [\n ...(result.failures ?? []),\n { dimension: 'commerce.intent.approval_required', message: APPROVAL_REASON },\n ];\n result.denialReasons = [APPROVAL_REASON, ...(result.denialReasons ?? [])];\n}\n\n/**\n * Extract step-up approval polling info from the verification result, if\n * present. Returns undefined when no approval was created (e.g. transaction\n * below the autonomous threshold, or the backend couldn't create one).\n */\nexport function getApprovalPollingInfo(result: VerificationResult): StepUpApprovalInfo | undefined {\n return result.stepUpApproval;\n}\n","/**\n * Step-up hold-and-poll (3.6.0) — the synchronous half of the MFA-style gate,\n * mirroring the Java SDK's `awaitStepUpApproval` / `pollStepUpStatus`.\n *\n * A verify() that comes back step_up_required carries a `stepUpApproval`\n * handle: `{ approvalId, pollUrl, expiresAt }`. The adapter HOLDS the original\n * request, polls the approval status, and on 'approved' re-verifies the same\n * request once (cache-bypassed) so the backend redeems the single-use approval\n * and returns a grant. Any other terminal outcome falls into the adapter's\n * normal fail-closed deny path.\n *\n * The poll endpoint is deliberately unauthenticated (it sits above the\n * backend router's `authenticate` middleware): the unguessable UUIDv4\n * approvalId is the capability token, and the route is rate-limited\n * (60/min/IP). No Authorization header is sent.\n */\n\nimport { sdkFetch } from '../http';\nimport { GatewayConfig, StepUpApprovalInfo, StepUpApprovalStatus, StepUpOutcome } from '../types';\n\nconst DEFAULT_POLL_INTERVAL_MS = 3000;\nconst MIN_POLL_INTERVAL_MS = 250;\nconst FALLBACK_MAX_WAIT_MS = 5 * 60 * 1000;\nconst EXPIRY_GRACE_MS = 5000;\nconst POLL_REQUEST_TIMEOUT_MS = 10_000;\n\n/** Resolve the absolute poll URL. pollUrl is server-relative\n * (`/api/step-up-approvals/poll/:id`), so join it to apiBaseUrl minus its\n * `/api` suffix (the origin) — same idiom as well-known.ts. Absolute URLs\n * pass through untouched. */\nfunction resolvePollUrl(config: GatewayConfig, pollUrl: string): string {\n if (pollUrl.startsWith('http')) return pollUrl;\n const origin = (config.apiBaseUrl ?? '').replace(/\\/api\\/?$/, '');\n return origin + pollUrl;\n}\n\n/**\n * One GET of the approval's poll endpoint. Returns the status string, or\n * null on any error / non-2xx / unparsable body — the hold loop treats null\n * as transient and just polls again.\n */\nexport async function pollStepUpStatus(\n config: GatewayConfig,\n approval: StepUpApprovalInfo\n): Promise<StepUpApprovalStatus | null> {\n if (!approval.pollUrl) return null;\n try {\n const res = await sdkFetch(resolvePollUrl(config, approval.pollUrl), {\n method: 'GET',\n signal: AbortSignal.timeout(POLL_REQUEST_TIMEOUT_MS),\n });\n if (!res.ok) return null;\n const body = (await res.json()) as Record<string, unknown>;\n // Envelope shape: { success, data: { status, resolvedAt } } — a flat\n // `status` read hangs every hold (live-UAT catch). Envelope-first with a\n // flat fallback for forward compatibility, exactly like the Java SDK.\n const data = body?.data;\n const status =\n data && typeof data === 'object' ? (data as Record<string, unknown>).status : body?.status;\n return typeof status === 'string' ? (status as StepUpApprovalStatus) : null;\n } catch {\n return null; // transient poll failure — the loop just polls again\n }\n}\n\nfunction resolveDeadline(config: GatewayConfig, approval: StepUpApprovalInfo): number {\n const now = Date.now();\n const maxWait = config.stepUpMaxWaitMs ?? 0;\n if (maxWait > 0) return now + maxWait;\n // stepUpMaxWaitMs === 0: hold until the approval itself expires (+ a small\n // grace so an approval landing at the buzzer still resolves on the next\n // poll). Missing/unparsable expiresAt → fixed 5-min fallback (Java parity).\n if (approval.expiresAt) {\n const expiry = Date.parse(approval.expiresAt);\n if (!Number.isNaN(expiry)) return Math.max(now, expiry) + EXPIRY_GRACE_MS;\n }\n return now + FALLBACK_MAX_WAIT_MS;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Block until a pending step-up approval reaches a terminal state or the wait\n * budget runs out. Returns the terminal status, or 'timeout'.\n *\n * Unknown non-pending statuses (a future backend state) still terminate the\n * hold — they are returned as-is and land in the deny path, which is the safe\n * reading of \"not approved\".\n */\nexport async function awaitStepUpApproval(\n config: GatewayConfig,\n approval: StepUpApprovalInfo\n): Promise<StepUpOutcome> {\n const deadline = resolveDeadline(config, approval);\n const pollInterval = Math.max(\n MIN_POLL_INTERVAL_MS,\n config.stepUpPollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS\n );\n\n while (Date.now() < deadline) {\n const status = await pollStepUpStatus(config, approval);\n if (status && status !== 'pending') {\n return status as StepUpOutcome;\n }\n const remaining = deadline - Date.now();\n if (remaining <= 0) break;\n await sleep(Math.min(pollInterval, remaining));\n }\n return 'timeout';\n}\n","/**\n * X-Astra-Verified-Hop — cross-hop verify-access dedupe marker.\n *\n * Header carrying upstream verify-access proof so an inner-hop endpoint can\n * dedupe (skip verify-access when the same ASTRA-id was already verified a\n * few ms earlier). Emitted by the MCP adapter toward tool fetches and by the\n * edge gateway toward the origin at authenticate/authorize depth. Value\n * format:\n *\n * {astraId};{sessionId};{checkedAt-ms}\n *\n * Receivers MUST validate that `checkedAt` is recent (≤ 60s window\n * recommended) and that `astraId` matches the agent identity claimed on the\n * inner hop. The header alone is NOT proof-of-identity — it's an UNSIGNED\n * dedupe advisory: trust it (`trustVerifiedHop`) only where every network\n * path to the inner hop crosses a stripping gateway or an equivalent trusted\n * boundary, exactly like the unsigned X-AstraSync-* attestation headers.\n * Pair with the existing X-Astra-Id auth.\n */\n\nexport const MCP_VERIFIED_HOP_HEADER = 'X-Astra-Verified-Hop';\n\n/** Default acceptable age (ms) for an X-Astra-Verified-Hop header. */\nexport const MCP_VERIFIED_HOP_MAX_AGE_MS = 60_000;\n\nexport interface VerifiedHopMarker {\n astraId: string;\n sessionId?: string;\n checkedAt: number; // epoch ms\n}\n\nexport function serializeVerifiedHop(marker: VerifiedHopMarker): string {\n return `${marker.astraId};${marker.sessionId ?? ''};${marker.checkedAt}`;\n}\n\nexport function parseVerifiedHop(value: string | undefined | null): VerifiedHopMarker | null {\n if (!value) return null;\n const parts = value.split(';');\n if (parts.length !== 3) return null;\n const [astraId, sessionId, checkedAtRaw] = parts;\n if (!astraId) return null;\n const checkedAt = Number(checkedAtRaw);\n if (!Number.isFinite(checkedAt) || checkedAt <= 0) return null;\n return {\n astraId,\n ...(sessionId ? { sessionId } : {}),\n checkedAt,\n };\n}\n\n/**\n * Returns true when `marker.astraId` matches the inner-hop's claimed\n * ASTRA-id AND the marker is recent enough. Inner-hop middleware uses this\n * to skip a duplicate verify-access call.\n */\nexport function isVerifiedHopValidFor(\n marker: VerifiedHopMarker | null,\n expectedAstraId: string,\n opts: { maxAgeMs?: number; now?: number } = {}\n): boolean {\n if (!marker) return false;\n if (marker.astraId !== expectedAstraId) return false;\n const maxAge = opts.maxAgeMs ?? MCP_VERIFIED_HOP_MAX_AGE_MS;\n const now = opts.now ?? Date.now();\n return now - marker.checkedAt <= maxAge && now >= marker.checkedAt;\n}\n","/**\n * MCP server-side helpers — companion to `transport/mcp.ts` (which handles the\n * agent-side `_meta.astrasync` block).\n *\n * Surfaces a body-aware policy hook the existing `createMiddleware` couldn't\n * provide — MCP traffic is JSON-RPC over a single endpoint (`/mcp`), so the\n * default route-pattern gating is too coarse: every request looks the same\n * URL-wise, but `initialize` is low-risk handshake while `tools/call` of a\n * payment tool is high-risk. Cohort-3 beta merchants flagged this 🟡 in the\n * v2.9.5 round.\n *\n * What lives here:\n * - `parseMcpJsonRpc(body)` — peels JSON-RPC method + tool name + agent\n * id without committing to a particular MCP\n * server framework.\n * - `mcpToPdlss(parsed)` — canonical mapping JSON-RPC method → PDLSS\n * purpose / action / resource. Doc-stable\n * so audits can correlate.\n * - `mcpDefaultEnforced(parsed)` — whether a method is enforced by default\n * so a single MCP middleware can split\n * `initialize` / `tools/list` (pass-through)\n * from `tools/call` (enforced).\n * - `MCP_VERIFIED_HOP_HEADER` — header convention for the dedupe pattern\n * when an MCP tool calls an inner REST hop.\n * - `serialize/parseVerifiedHop` helpers.\n *\n * The Express MCP adapter is in `adapters/mcp.ts` and consumes these.\n */\n\n// X-Astra-Verified-Hop helpers live in ../verified-hop (shared with the\n// edge gateway's origin-bound emission); re-exported here so existing\n// transport-level imports keep working unchanged.\nexport {\n MCP_VERIFIED_HOP_HEADER,\n MCP_VERIFIED_HOP_MAX_AGE_MS,\n serializeVerifiedHop,\n parseVerifiedHop,\n isVerifiedHopValidFor,\n} from '../verified-hop';\nexport type { VerifiedHopMarker } from '../verified-hop';\n\n/**\n * Output of `parseMcpJsonRpc`. Self-describing so the middleware doesn't have\n * to re-introspect the body to figure out gating.\n */\nexport interface ParsedMcpRequest {\n /** JSON-RPC method (e.g. `tools/call`, `initialize`, `tools/list`). */\n method: string;\n /** Set when method === 'tools/call'; the tool name from `params.name`. */\n toolName?: string;\n /** Initialize-specific protocolVersion handshake info, when present. */\n protocolVersion?: string;\n /**\n * Agent id read from the body, in priority order:\n * 1. `params._meta.astrasync.agentId` (the canonical SDK location, see\n * `transport/mcp.ts → setMcpMeta`)\n * 2. `params.arguments.agent_id` (legacy / hand-written tool callers)\n * Header-supplied id (X-Astra-Id) is read separately by the adapter and\n * compared to this for the mismatch check.\n */\n agentIdFromBody?: string;\n /**\n * Purpose extracted from the MCP body\n * with the symmetric precedence chain. Sourced from `_meta.astrasync.purpose`\n * (canonical SDK location) OR `params.arguments.purpose` (legacy /\n * conventional callers). The discriminator is on `purposeSourceFromBody`.\n * Adapter combines this with the `X-Astra-Purpose` header (header wins)\n * before mapping; final fallback at `mcpToPdlss` is `undefined`.\n */\n purposeFromBody?: string;\n /** Which body location resolved `purposeFromBody`. */\n purposeSourceFromBody?: 'meta' | 'tool_argument';\n /**\n * Action extracted from the MCP body with the same\n * symmetric chain as purpose. Sourced from `_meta.astrasync.action`\n * (canonical) OR `params.arguments.action` (legacy). Adapter combines\n * with the `X-Astra-Action` header (header wins) before mapping; final\n * fallback at `mcpToPdlss` is the transport-layer default\n * (`tools/call:<toolname>` or just `<method>`).\n */\n actionFromBody?: string;\n /** Which body location resolved `actionFromBody`. */\n actionSourceFromBody?: 'meta' | 'tool_argument';\n /** True for handshake methods that must succeed before any tool call. */\n isInitialize: boolean;\n /** True for `tools/call`. */\n isToolCall: boolean;\n /** True for low-risk introspection (`tools/list`, `prompts/list`, etc.). */\n isIntrospection: boolean;\n}\n\n/**\n * Peel the JSON-RPC envelope. Returns `null` if the body isn't a JSON-RPC\n * request (callers can short-circuit with a 400 or treat as untyped traffic).\n *\n * Accepts both single-request and notification shapes. Batch requests are\n * NOT supported here — the verify-access contract is single-agent-per-call;\n * a batch body should be split before policy gating.\n */\nexport function parseMcpJsonRpc(body: unknown): ParsedMcpRequest | null {\n if (!body || typeof body !== 'object' || Array.isArray(body)) return null;\n const obj = body as Record<string, unknown>;\n if (obj.jsonrpc !== '2.0' && obj.jsonrpc !== '1.0') return null;\n const method = typeof obj.method === 'string' ? obj.method : null;\n if (!method) return null;\n const params = obj.params as Record<string, unknown> | undefined;\n\n // Tool-name lookup. MCP `tools/call` puts the tool under `params.name`.\n let toolName: string | undefined;\n if (method === 'tools/call' && params && typeof params.name === 'string') {\n toolName = params.name;\n }\n\n // protocolVersion only on the initialize request.\n let protocolVersion: string | undefined;\n if (method === 'initialize' && params && typeof params.protocolVersion === 'string') {\n protocolVersion = params.protocolVersion;\n }\n\n const meta = params?._meta as Record<string, unknown> | undefined;\n const astrasyncMeta = meta?.astrasync as Record<string, unknown> | undefined;\n const args = params?.arguments as Record<string, unknown> | undefined;\n\n // Agent-id lookup with documented precedence: _meta.astrasync.agentId\n // wins over params.arguments.agent_id. (The argument-shape uses\n // `agent_id` snake_case for legacy conventional callers; meta is the\n // canonical SDK location.)\n let agentIdFromBody: string | undefined;\n if (astrasyncMeta && typeof astrasyncMeta.agentId === 'string') {\n agentIdFromBody = astrasyncMeta.agentId;\n } else if (args && typeof args.agent_id === 'string') {\n agentIdFromBody = args.agent_id;\n }\n\n // Round-13 (R13-1, R13-2) — symmetric body-extraction for purpose +\n // action. Per `feedback_symmetric_fallbacks_for_symmetric_concepts.md`:\n // these two concepts are paired in the MCP middleware, so their\n // fallback chains MUST mirror each other exactly. Drift between them\n // generates the same support-ticket class repeatedly (\"I set X in\n // arguments and it didn't take\" — happened for purpose in round-12\n // post-F19, and was about to happen for action in round-13).\n //\n // Body precedence (applies to both): _meta.astrasync.<key> wins over\n // params.arguments.<key>. The discriminator is exported alongside the\n // value so the adapter can fold it into purposeSource / actionSource\n // for debug logging and adoption tracking.\n const purposeBodyResult = extractFromMcpBody(astrasyncMeta, args, 'purpose');\n const actionBodyResult = extractFromMcpBody(astrasyncMeta, args, 'action');\n\n const isInitialize = method === 'initialize';\n const isToolCall = method === 'tools/call';\n const isIntrospection =\n method === 'tools/list' ||\n method === 'prompts/list' ||\n method === 'resources/list' ||\n method === 'ping' ||\n method === 'notifications/initialized';\n\n return {\n method,\n ...(toolName ? { toolName } : {}),\n ...(protocolVersion ? { protocolVersion } : {}),\n ...(agentIdFromBody ? { agentIdFromBody } : {}),\n ...(purposeBodyResult.value ? { purposeFromBody: purposeBodyResult.value } : {}),\n ...(purposeBodyResult.source ? { purposeSourceFromBody: purposeBodyResult.source } : {}),\n ...(actionBodyResult.value ? { actionFromBody: actionBodyResult.value } : {}),\n ...(actionBodyResult.source ? { actionSourceFromBody: actionBodyResult.source } : {}),\n isInitialize,\n isToolCall,\n isIntrospection,\n };\n}\n\n/**\n * Shared body-extraction helper for the symmetric purpose + action\n * fallback chain. Returns the resolved value AND\n * the source discriminator so adapters can fold it into per-call debug\n * logs (`purpose_source` / `action_source`).\n *\n * Precedence:\n * 1. `params._meta.astrasync.<key>` — canonical SDK location, source: 'meta'\n * 2. `params.arguments.<key>` — legacy / conventional, source: 'tool_argument'\n * 3. neither present — value: undefined, source: undefined\n *\n * The HTTP-header tier (`X-Astra-<concept>`) is the adapter's job; this\n * helper only handles body sources. Adapters combine the two: header\n * wins over body. See `mcpToPdlss` for the final precedence assembly.\n */\nfunction extractFromMcpBody(\n astrasyncMeta: Record<string, unknown> | undefined,\n args: Record<string, unknown> | undefined,\n key: 'purpose' | 'action'\n): { value: string | undefined; source: 'meta' | 'tool_argument' | undefined } {\n if (astrasyncMeta && typeof astrasyncMeta[key] === 'string') {\n return { value: astrasyncMeta[key] as string, source: 'meta' };\n }\n if (args && typeof args[key] === 'string') {\n return { value: args[key] as string, source: 'tool_argument' };\n }\n return { value: undefined, source: undefined };\n}\n\n/**\n * PDLSS mapping for an MCP request. The platform's PDLSS taxonomy is\n * `purpose / action / resource`; for MCP traffic the audit-useful dimensions\n * are the JSON-RPC method and (for `tools/call`) the tool name.\n *\n * v2.5.0 rules:\n * - `resource` defaults to the HTTP request path (e.g. `/mcp`). Per-tool\n * overrides via `toolGates` config let merchants map tools to specific\n * backend resources (e.g. `/api/catalog`).\n * - `purpose` defaults to `undefined` (backend evaluator applies\n * skip-when-undefined). Per-tool overrides via `toolGates` config let\n * merchants declare the semantic purpose each tool fulfils.\n * - `action` comes only from declarations (toolGate config, header, or\n * body). For `tools/call` with none declared it is `undefined` —\n * evaluation is purpose-only, because a raw tool name is a transport\n * token, not a PDLSS action, and sending it as one guarantees an\n * action-policy mismatch. For other JSON-RPC methods the method string\n * is used.\n */\nexport interface McpPdlssMapping {\n purpose: string | undefined;\n /**\n * `undefined` when a `tools/call` request declared no action anywhere —\n * the verify-access call then evaluates purpose-only. Tool names never\n * travel as PDLSS actions.\n */\n action: string | undefined;\n resource: string;\n purposeSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | undefined;\n actionSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | 'transport_layer' | undefined;\n}\n\n/**\n * v2.5.0 — PDLSS field derivation for MCP requests.\n *\n * Purpose precedence:\n * - If `toolGate.purpose` is provided, it is authoritative (header/body ignored).\n * - Otherwise: header → body `_meta` → body `arguments` → `undefined`.\n *\n * Resource precedence:\n * - `toolGate.resource` if provided, else `requestPath`.\n *\n * Action precedence (3.1.0 — toolGate override added for\n * symmetry with purpose; 4.1.0 — the bare-tool-name fallback for\n * `tools/call` was removed):\n * - `toolGate.action` authoritative → header → body `_meta` → body\n * `arguments` → then: `undefined` for `tools/call` (purpose-only\n * evaluation — tool names are transport tokens, never PDLSS actions),\n * or the JSON-RPC method string for other methods (transport_layer).\n *\n * @param requestPath The HTTP request path (e.g. '/mcp'). Required.\n * @param toolGate Resolved per-tool config from `toolGates`, if present.\n */\nexport function mcpToPdlss(\n parsed: ParsedMcpRequest,\n requestPath: string,\n headerPurpose?: string,\n headerAction?: string,\n toolGate?: { purpose?: string; action?: string; resource?: string }\n): McpPdlssMapping {\n const resource = toolGate?.resource ?? requestPath;\n\n let purpose: string | undefined;\n let purposeSource: McpPdlssMapping['purposeSource'];\n if (toolGate?.purpose !== undefined) {\n purpose = toolGate.purpose;\n purposeSource = 'tool_gate';\n } else if (headerPurpose) {\n purpose = headerPurpose;\n purposeSource = 'header';\n } else if (parsed.purposeFromBody && parsed.purposeSourceFromBody) {\n purpose = parsed.purposeFromBody;\n purposeSource = parsed.purposeSourceFromBody;\n } else {\n purpose = undefined;\n purposeSource = undefined;\n }\n\n let action: string | undefined;\n let actionSource: McpPdlssMapping['actionSource'];\n if (toolGate?.action !== undefined) {\n action = toolGate.action;\n actionSource = 'tool_gate';\n } else if (headerAction) {\n action = headerAction;\n actionSource = 'header';\n } else if (parsed.actionFromBody && parsed.actionSourceFromBody) {\n action = parsed.actionFromBody;\n actionSource = parsed.actionSourceFromBody;\n } else if (parsed.method === 'tools/call') {\n // 4.1.0 fix: previously the bare tool name travelled as the PDLSS\n // action here, which guaranteed an action-policy deny for merchants\n // using the bare-string toolGate shorthand (tool names are transport\n // tokens, not policy vocabulary). With nothing configured or declared,\n // leave the action undeclared so evaluation is purpose-only.\n action = undefined;\n actionSource = undefined;\n } else {\n action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;\n actionSource = 'transport_layer';\n }\n\n return { purpose, action, resource, purposeSource, actionSource };\n}\n\n/**\n * Whether a method is enforced (calls verify-access + gates on the server\n * decision) by default. The MCP middleware uses this to split low-risk\n * handshake / introspection traffic (pass-through) from high-risk tool\n * execution (enforced). Per-tool / per-method overrides win over this default.\n *\n * - `initialize` / `notifications/initialized` → false (handshake must work for unregistered probes)\n * - `tools/list` / `prompts/list` / `resources/list` → false (introspection is public-surface)\n * - `ping` → false\n * - `resources/read` → true\n * - `tools/call` → true (default — overridable per-tool)\n * - everything else → true (least-privilege fallback)\n */\nexport function mcpDefaultEnforced(parsed: ParsedMcpRequest): boolean {\n if (parsed.isInitialize || parsed.method === 'notifications/initialized') return false;\n if (parsed.isIntrospection) return false;\n return true;\n}\n","/**\n * AstraSync Universal Verification Gateway — MCP middleware\n *\n * Express-shaped middleware tailored to the JSON-RPC body of an MCP\n * (Model Context Protocol) endpoint. Closes the cohort-3 gaps the default\n * `createMiddleware` couldn't:\n *\n * (a) **Body-aware gating**. All MCP traffic targets the same `/mcp` URL.\n * The default route-pattern matcher can't tell `initialize` (low risk)\n * from `tools/call start_checkout` (high risk). This middleware peels\n * the JSON-RPC body and applies a per-method risk tier.\n *\n * (b) **PDLSS mapping**. Derives purpose, action, and resource from\n * `toolGates` config and request context. See `transport/mcp-server.ts`\n * `mcpToPdlss()` for the exact mapping.\n *\n * (c) **Inner-hop dedupe**. Outbound responses set\n * `X-Astra-Verified-Hop` so a downstream REST endpoint that the tool\n * calls can skip a duplicate verify-access. The receiving REST\n * middleware checks `parseVerifiedHop` and skips when valid.\n *\n * (d) **Header-vs-body identity precedence**. Reads ASTRA-id from\n * `X-Astra-Id` first, body second. If both are present and disagree,\n * returns a structured 400 by default (configurable). Pre-fix,\n * integrators had to re-discover this on their own.\n *\n * Usage:\n *\n * ```typescript\n * import express from 'express';\n * import { createMcpMiddleware } from '@astrasyncai/verification-gateway/mcp';\n *\n * const app = express();\n * app.use(express.json());\n *\n * app.post(\n * '/mcp',\n * createMcpMiddleware({\n * apiBaseUrl: 'https://astrasync.ai/api',\n * apiKey: process.env.ASTRASYNC_API_KEY,\n * // Per-tool gates — `tools/call` is enforced by default; introspection /\n * // handshake pass through. A gated tool needs a PDLSS purpose (and ideally\n * // an action). Set a tool/method to `'observe'` to pass it through\n * // unenforced (still evaluated when `evaluateAlwaysIfCredentialed`).\n * toolGates: {\n * browse_catalog: { purpose: 'shopping', action: 'shopping.search' },\n * start_checkout: { purpose: 'shopping', action: 'shopping.purchase' },\n * health_check: 'observe',\n * },\n * }),\n * yourMcpServerHandler,\n * );\n * ```\n */\n\nimport type { Request, Response, NextFunction, RequestHandler } from 'express';\nimport type {\n GatewayConfig,\n VerificationResult,\n EnhancedVerificationResult,\n VerificationRequest,\n} from '../types';\nimport { verify, extractCredentials, recordDecision } from '../verify';\nimport { buildSdkObservedMetadata } from '../metadata-capture';\nimport { requiresHumanApproval, annotateApprovalRequired } from './approval-gate';\nimport { awaitStepUpApproval } from './stepup-poll';\nimport { prefetchWellKnown, getWellKnownUrls } from '../well-known';\nimport {\n parseMcpJsonRpc,\n mcpToPdlss,\n mcpDefaultEnforced,\n serializeVerifiedHop,\n parseVerifiedHop,\n isVerifiedHopValidFor,\n MCP_VERIFIED_HOP_HEADER,\n type ParsedMcpRequest,\n} from '../transport/mcp-server';\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n mcpRequest?: ParsedMcpRequest;\n // Declared here as well as in express.ts (identical type — the merged\n // interfaces are compatible) so this module typechecks standalone: the\n // grouped DTS build compiles each entry group as its own program, and\n // mcp.ts must not depend on express.ts being in the same one.\n agentVerification?: VerificationResult;\n }\n }\n}\n\n/**\n * Extended per-tool gate with optional PDLSS purpose + action + resource\n * overrides.\n *\n * When `purpose` is set, it is authoritative for that tool — the agent's\n * `X-Astra-Purpose` header is ignored. This lets the merchant declare what\n * semantic purpose each tool fulfils rather than trusting agent self-declaration.\n *\n * When `action` is set (symmetric with `purpose`), it is\n * authoritative over `X-Astra-Action` / body declarations, letting the\n * merchant pin a dotted-verb action (e.g. `shopping.search`) for a tool\n * whose callers would otherwise leave the action undeclared (purpose-only\n * evaluation — raw tool names never travel as PDLSS actions).\n *\n * When `resource` is set, it overrides the default (`req.path`) for that\n * tool's verify-access call — e.g. mapping `list_products` to `/api/catalog`.\n */\nexport interface ToolGateConfig {\n purpose?: string;\n action?: string;\n resource?: string;\n}\n\n/**\n * A tool/method gate is either a {@link ToolGateConfig} (enforced — calls\n * verify-access and gates on the server decision, using the declared PDLSS\n * purpose/action) or the literal `'observe'` (passed through unenforced;\n * still evaluated when `evaluateAlwaysIfCredentialed` is set). SDK 5.0.0\n * removed the access-level band, so a gate no longer carries a tier — its\n * only decision is enforce vs observe.\n */\nexport type ToolGate = ToolGateConfig | 'observe';\n\nexport interface McpMiddlewareOptions extends GatewayConfig {\n /**\n * Per-tool gating for `tools/call` invocations. Tools not listed are\n * enforced by default (`tools/call` calls verify-access and gates on the\n * server decision).\n *\n * A gated tool declares its PDLSS `purpose` (required: the backend rejects\n * gated calls with no resolvable purpose) and ideally a dotted-verb\n * `action`:\n * ```typescript\n * toolGates: {\n * list_products: { purpose: 'shopping',\n * action: 'shopping.search',\n * resource: '/api/catalog' },\n * start_checkout: { purpose: 'shopping',\n * action: 'shopping.purchase',\n * resource: '/api/checkout/*' },\n * health_check: 'observe', // pass through unenforced\n * }\n * ```\n *\n * Set a tool to `'observe'` to pass it through without enforcement (it is\n * still evaluated for the audit trail when `evaluateAlwaysIfCredentialed`\n * is set). A gated object form with no resolvable purpose fails fast with a\n * `PDLSS_PURPOSE_REQUIRED` 400 (the SDK never sends the raw tool name as\n * the PDLSS action).\n *\n * The action axis for non-tools/call MCP methods (`tools/list`,\n * `resources/list`, `prompts/list`, etc.) is the literal JSON-RPC method\n * name (e.g. `'tools/list'`). Merchants gating MCP traffic by action should\n * declare per-tool gates in `toolGates`, not endpoint-level `allowedActions`\n * — the latter applies to REST-style action values, not MCP method strings.\n */\n toolGates?: Record<string, ToolGate>;\n /**\n * Per-method override. By default introspection / handshake methods\n * (`tools/list`, `initialize`, `ping`, …) pass through and `tools/call` is\n * enforced; declare a method here to enforce it (object form, with a PDLSS\n * purpose) or set it to `'observe'` to pass it through. Matches by exact\n * JSON-RPC method string.\n */\n methodGates?: Record<string, ToolGate>;\n /**\n * What to do when the agent id supplied in the X-Astra-Id header\n * disagrees with the agent id in the JSON-RPC body\n * (`params._meta.astrasync.agentId` or `params.arguments.agent_id`).\n *\n * - `'reject'` (default) — return 400 `AGENT_ID_MISMATCH`. Safest.\n * - `'prefer-header'` — log + verify against the header value. Keeps\n * bodies that were authored before X-Astra-Id was the canonical\n * identity slot working.\n * - `'prefer-body'` — log + verify against the body value. Useful in\n * reverse-proxy setups that strip auth headers.\n */\n onAgentIdMismatch?: 'reject' | 'prefer-header' | 'prefer-body';\n /** Skip verification + dedupe entirely. For testing. */\n skip?: boolean;\n /** Custom denied handler. Defaults to a structured JSON-RPC error response. */\n onDenied?: (result: VerificationResult, req: Request, res: Response) => void;\n /**\n * If `true`, trust an inbound `X-Astra-Verified-Hop` header to skip\n * verify-access when it carries a recent marker for the resolved agent.\n *\n * **DEFAULT FLIPPED TO `false` in SDK 2.4.13.** The marker\n * is plaintext semicolon-delimited with no HMAC, so any client that sets\n * the header (with the right format + fresh timestamp + matching ASTRA-id)\n * skipped verify-access entirely. The per-process verify-access result\n * cache already dedupes legitimate repeat calls — this optimization\n * provided no additional savings, only a bypass surface.\n *\n * Set this to `true` ONLY if you control every upstream MCP hop that sets\n * the header AND you trust the network path between hops (mTLS, internal\n * VPC, etc.). For most installs, leave at the safe default.\n *\n * A future SDK release will either remove this option entirely OR add\n * HMAC signing to the marker.\n */\n trustVerifiedHop?: boolean;\n /** Window for accepting an upstream verified-hop marker. Default 60_000ms. */\n verifiedHopMaxAgeMs?: number;\n /**\n * Automatically record grant/deny decisions for every MCP call. Default\n * `true` — matches the express adapter.\n */\n recordDecisions?: boolean;\n /** Forward runtime challenge (default `true`). */\n enableRuntimeChallenge?: boolean;\n /**\n * Posture when the MCP middleware itself throws an internal error.\n * Default `'open'` for backward compatibility in SDK 2.4.13. Shadow logs\n * record what WOULD be denied with `failOnError: 'closed'` so merchants\n * can grep correlationId for impact analysis during the observation\n * window. Default flips to `'closed'` in a follow-up release.\n */\n failOnError?: 'open' | 'closed';\n}\n\n/**\n * Default MCP denied handler.\n *\n * Mirrors the express adapter — surfaces `failures[]`\n * and `correlationId` on the response so partners can render rich UX and\n * tie a denial to a log line. Also stamps `X-Astra-Gateway-Mode: enforced`\n * so the header reliably distinguishes gate-evaluated denials\n * from gate-skipped pass-throughs.\n */\n/**\n * Shared single-value HTTP header reader. Same shape used for\n * both `X-Astra-Purpose` and `X-Astra-Action` extraction (symmetric\n * fallback chain — see `mcpToPdlss` for the full precedence rule).\n * Returns the first value if the header repeats (Express turns repeated\n * headers into an array); returns undefined when the header is absent.\n */\nfunction readSingleHeader(value: string | string[] | undefined): string | undefined {\n if (typeof value === 'string') return value;\n if (Array.isArray(value)) return value[0];\n return undefined;\n}\n\nfunction dedupeFailures(result: VerificationResult): void {\n if (result.failures && result.failures.length > 1) {\n const seen = new Set<string>();\n result.failures = result.failures.filter((f) => {\n const key = `${f.dimension}|${f.message}|${(f as { guidance?: string }).guidance ?? ''}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n }\n if (result.denialReasons && result.denialReasons.length > 1) {\n result.denialReasons = [...new Set(result.denialReasons)];\n }\n}\n\nfunction defaultMcpDenied(result: VerificationResult, req: Request, res: Response): void {\n // JSON-RPC error envelope so MCP-aware clients render it correctly.\n const id = (req.body as Record<string, unknown> | undefined)?.id ?? null;\n\n // 4.0.0 BREAKING: the integration's OWN key was rejected — not the agent's\n // fault, not transient. 503 + a dedicated JSON-RPC code so MCP clients can\n // tell \"this server's verification layer is broken\" from their own denial.\n if (result.misconfigured) {\n res.setHeader('X-Astra-Gateway-Mode', 'enforced');\n res.status(503).json({\n jsonrpc: '2.0',\n id,\n error: {\n // -32002 → merchant verification misconfigured (see -32000/-32001 below)\n code: -32002,\n message:\n result.denialReasons?.[0] ?? \"This server's AstraSync integration is misconfigured\",\n data: {\n guidance: result.guidance,\n failures: result.failures,\n correlationId: result.correlationId,\n },\n },\n });\n return;\n }\n\n // Round-18 G4: see express.defaultOnDenied for full rationale on the\n // identity-vs-policy split → 401/403 mapping. Same logic here so MCP\n // clients get HTTP status semantics aligned with REST adapters.\n const status = !result.identityVerified ? 401 : 403;\n\n res.setHeader('X-Astra-Gateway-Mode', 'enforced');\n\n res.status(status).json({\n jsonrpc: '2.0',\n id,\n error: {\n // JSON-RPC error codes:\n // -32000 → unauthorized (no identity resolved)\n // -32001 → insufficient access (identity OK, policy denied)\n // -32002 → merchant verification misconfigured (handled above)\n code: !result.identityVerified ? -32000 : -32001,\n message: result.denialReasons?.[0] ?? 'Access denied',\n data: {\n guidance: result.guidance,\n // Round-10: aggregated per-dimension detail + correlation handle.\n failures: result.failures,\n correlationId: result.correlationId,\n stepUpApproval: result.stepUpApproval,\n // 3.6.0: terminal outcome of an opt-in hold-and-poll wait (denied /\n // expired / consumed / timeout). Absent when the feature is off.\n ...(result.stepUpOutcome && { stepUpOutcome: result.stepUpOutcome }),\n },\n },\n });\n}\n\n/**\n * Resolve whether a parsed MCP request is enforced, and the gate config to\n * apply. SDK 5.0.0 removed the access-level band — a gate's only decision is\n * enforce vs observe.\n *\n * Precedence:\n * 1. Non-tools/call without `methodGates` entry → pass through\n * (`discovery_default`). Discovery is open — agents must list tools\n * before invoking them.\n * 2. `methodGates[method]` — per-method override (`'observe'` or a config)\n * 3. `toolGates[toolName]` — per-tool override (tools/call only)\n * 4. `mcpDefaultEnforced` — built-in default (tools/call → enforced)\n *\n * The `source` field is logged for forensics / activity-feed attribution.\n */\nfunction resolveGate(\n parsed: ParsedMcpRequest,\n opts: {\n toolGates?: Record<string, ToolGate>;\n methodGates?: Record<string, ToolGate>;\n }\n): {\n enforce: boolean;\n config?: ToolGateConfig;\n source: 'toolGate' | 'methodGate' | 'tier' | 'discovery_default';\n} {\n if (!parsed.toolName) {\n const methodGate = opts.methodGates?.[parsed.method];\n if (methodGate !== undefined) {\n return methodGate === 'observe'\n ? { enforce: false, source: 'methodGate' }\n : { enforce: true, config: methodGate, source: 'methodGate' };\n }\n // Non-tools/call methods (handshake / introspection) pass through unless a\n // methodGate opts them in — preserves open discovery for unregistered probes.\n return { enforce: false, source: 'discovery_default' };\n }\n const toolGate = opts.toolGates?.[parsed.toolName];\n if (toolGate !== undefined) {\n return toolGate === 'observe'\n ? { enforce: false, source: 'toolGate' }\n : { enforce: true, config: toolGate, source: 'toolGate' };\n }\n // tools/call with no explicit gate → enforced by default.\n return { enforce: mcpDefaultEnforced(parsed), source: 'tier' };\n}\n\n/**\n * Create the MCP middleware. Attach AFTER `express.json()` — the body must\n * already be a parsed object.\n */\nexport function createMcpMiddleware(options: McpMiddlewareOptions): RequestHandler {\n const {\n toolGates,\n methodGates,\n onAgentIdMismatch = 'reject',\n skip = false,\n onDenied = defaultMcpDenied,\n trustVerifiedHop = false,\n verifiedHopMaxAgeMs,\n recordDecisions,\n enableRuntimeChallenge = true,\n failOnError = 'open',\n ...config\n } = options;\n\n // v2.5.0: fire-and-forget pre-fetch of well-known URLs\n if (config.apiBaseUrl) {\n prefetchWellKnown(config.apiBaseUrl).catch(() => {});\n }\n\n return async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n if (skip) return next();\n\n const parsed = parseMcpJsonRpc(req.body);\n if (!parsed) {\n // Not a JSON-RPC body. Either the route is misconfigured or the\n // client sent a non-MCP payload. We don't 400 here — let the actual\n // MCP server handler render its own error so the middleware doesn't\n // pre-empt valid framework error shapes.\n if (config.setPassThroughHeader) {\n // Round-10 (#49, O10): `unenforced` describes the GATE state, not\n // the downstream response. Renamed from the ambiguous\n // `pass-through` (which partners read as \"request succeeded\").\n res.setHeader('X-Astra-Gateway-Mode', 'unenforced');\n res.setHeader('X-Astra-Gateway-Reason', 'non-jsonrpc-body');\n }\n return next();\n }\n req.mcpRequest = parsed;\n\n // ── Identity reconciliation (defect d) ─────────────────────────────\n // Header value is the SDK-canonical slot; body slot exists for legacy\n // hand-written tool callers and the MCP `_meta.astrasync.agentId`\n // convention.\n const headerRaw = req.headers['x-astra-id'] ?? req.headers['x-astra-agentid'];\n const headerAstraId =\n typeof headerRaw === 'string'\n ? headerRaw\n : Array.isArray(headerRaw)\n ? headerRaw[0]\n : undefined;\n const bodyAstraId = parsed.agentIdFromBody;\n\n let effectiveAstraId: string | undefined;\n if (headerAstraId && bodyAstraId && headerAstraId !== bodyAstraId) {\n if (onAgentIdMismatch === 'reject') {\n const id = (req.body as Record<string, unknown>)?.id ?? null;\n res.status(400).json({\n jsonrpc: '2.0',\n id,\n error: {\n code: -32602,\n message: 'AGENT_ID_MISMATCH',\n data: {\n detail:\n 'The agent id in the X-Astra-Id header disagrees with params._meta.astrasync.agentId / params.arguments.agent_id. Reconcile before calling the tool — see https://astrasync.ai/docs/mcp-integration#identity-reconciliation.',\n headerAstraId,\n bodyAstraId,\n },\n },\n });\n return;\n }\n effectiveAstraId = onAgentIdMismatch === 'prefer-header' ? headerAstraId : bodyAstraId;\n } else {\n effectiveAstraId = headerAstraId ?? bodyAstraId;\n }\n\n // ── Inner-hop dedupe (defect c) ────────────────────────────────────\n // If a trusted upstream MCP hop already verified this exact ASTRA-id\n // a few ms ago, we accept the marker and skip a redundant verify-access\n // call. Only valid when the header is fresh AND matches the agent id\n // we resolved above.\n if (trustVerifiedHop && effectiveAstraId) {\n const hopRaw = req.headers[MCP_VERIFIED_HOP_HEADER.toLowerCase()];\n const hopValue =\n typeof hopRaw === 'string' ? hopRaw : Array.isArray(hopRaw) ? hopRaw[0] : undefined;\n const marker = parseVerifiedHop(hopValue);\n if (\n isVerifiedHopValidFor(marker, effectiveAstraId, {\n ...(verifiedHopMaxAgeMs !== undefined ? { maxAgeMs: verifiedHopMaxAgeMs } : {}),\n })\n ) {\n // Skip verify-access — pass straight through. Don't re-emit the\n // header since the MCP framework will issue the response (the\n // outer hop already tagged it for any further inner hops).\n return next();\n }\n }\n\n // ── Resolve gating ─────────────────────────────────────────────────\n const gateDecision = resolveGate(parsed, {\n toolGates,\n methodGates,\n });\n\n // ── Build verify-access request ────────────────────────────────────\n // Round-13 (R13-5) — credentials extraction hoisted above the\n // mcp-tier-none short-circuit (mirrors round-12 F9 express adapter\n // restructure). The `evaluateAlwaysIfCredentialed` flag lets the\n // caller turn unenforced tier-none calls into evaluated-not-enforced\n // calls when credentials are present, so the agent verification\n // result populates `req.agentVerification` for tier-aware handler\n // rendering (e.g. anonymous vs verified MCP tool listings).\n const credentials = extractCredentials(\n req.headers as Record<string, string | string[] | undefined>,\n req.query as Record<string, string | undefined>\n );\n // Override the SDK's default credential extraction with the resolved\n // ASTRA-id so the verify-access call sees the post-precedence value\n // (defect d). When neither header nor body supplied an id, the SDK's\n // anonymous-canonical-flow handles it.\n if (effectiveAstraId) credentials.astraId = effectiveAstraId;\n\n // Pass-through short-circuit. Default behaviour: skip verify-access for\n // observe / handshake / introspection (must work for unregistered\n // probes). With `evaluateAlwaysIfCredentialed: true` AND credentials\n // present, fall through to verify-access for the audit trail; the\n // post-verify gate-skipping branch below handles the rendering side.\n // Round-12 F9 parity (closes the round-12 deferral).\n const shouldEnforce = gateDecision.enforce;\n if (!shouldEnforce && (!config.evaluateAlwaysIfCredentialed || !credentials.astraId)) {\n if (config.setPassThroughHeader) {\n // Round-10 (#49, O10): `unenforced` describes the GATE state, not\n // the downstream response.\n res.setHeader('X-Astra-Gateway-Mode', 'unenforced');\n res.setHeader('X-Astra-Gateway-Reason', 'mcp-observe');\n }\n return next();\n }\n\n // Per-tool gate config (purpose + action + resource overrides).\n const gate = gateDecision.config;\n\n const headerPurpose = readSingleHeader(req.headers['x-astra-purpose']);\n const headerAction = readSingleHeader(req.headers['x-astra-action']);\n const pdlss = mcpToPdlss(\n parsed,\n req.path,\n headerPurpose,\n headerAction,\n gate ? { purpose: gate.purpose, action: gate.action, resource: gate.resource } : undefined\n );\n\n // Telemetry: debug log per resolution. Adoption tracking, support\n // triage, and the `default_*` / `transport_layer` decay signals\n // all read this. Both purpose and action emit their source so we\n // can spot which integration path each partner is using.\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.debug('[mcp-middleware] pdlss resolved', {\n purpose_source: pdlss.purposeSource,\n resolved_purpose: pdlss.purpose,\n action_source: pdlss.actionSource,\n resolved_action: pdlss.action,\n });\n }\n\n // 3.2.0 — clear \"purpose required\" error. We only reach here on the\n // ENFORCED path (the `!shouldEnforce` early-return is above). A gated\n // tool call with no resolvable PDLSS purpose (none on the toolGate, no\n // X-Astra-Purpose header, none in the body) would otherwise hit\n // verify-access's generic \"Invalid request data\" → surface to the agent\n // as an opaque `verify_access.api_error`. Fail fast with an actionable\n // message instead (purpose is required post-vocabulary-unification).\n if (!pdlss.purpose) {\n const id = (req.body as Record<string, unknown>)?.id ?? null;\n res.status(400).json({\n jsonrpc: '2.0',\n id,\n error: {\n code: -32602,\n message: 'PDLSS_PURPOSE_REQUIRED',\n data: {\n dimension: 'pdlss.purpose',\n detail:\n 'This tool is access-gated but the call declared no PDLSS purpose. Supply a bare-category purpose via the X-Astra-Purpose header or params._meta.astrasync.purpose, or have the merchant set the tool’s purpose in its toolGate config.',\n resolvedAction: pdlss.action,\n },\n },\n });\n return;\n }\n\n const counterpartyUrl =\n config.counterpartyUrl || `${req.protocol}://${req.get('host')}${req.path}`;\n const shouldRecordDecisions = recordDecisions !== false;\n\n const verificationRequest: VerificationRequest = {\n credentials,\n purpose: pdlss.purpose,\n action: pdlss.action,\n resource: pdlss.resource,\n // Round-12 (F19): mark transport protocol separately from intent.\n // The MCP middleware always sets this to 'mcp'; non-MCP callers\n // leave it unset (server-side default is 'rest').\n invocationProtocol: 'mcp',\n createSession: shouldRecordDecisions,\n counterpartyUrl,\n counterpartyType: config.counterpartyType || 'mcp_server',\n enableRuntimeChallenge,\n callerMetadata: {\n sourceIp: req.ip,\n userAgent: req.headers['user-agent'] as string | undefined,\n host: req.headers.host as string | undefined,\n // Capture EVERY inbound header (parity with the express adapter —\n // MCP-only merchants have no edge to do it upstream) plus the\n // CDN-derived connection block. Local string work, secrets\n // stripped; out of the verify cache key so it doesn't defeat\n // caching.\n observedMetadata: buildSdkObservedMetadata(req.headers),\n },\n };\n let result = await verify(config, verificationRequest);\n\n req.agentVerification = result;\n let sessionId = (result as EnhancedVerificationResult).sessionId;\n\n // 3.6.0 hold-and-poll (opt-in via stepUpMaxWaitMs) — see express.ts for\n // the full rationale. On 'approved' the same request is re-verified\n // once, cache-bypassed, so the backend redeems the single-use approval.\n if (\n config.stepUpMaxWaitMs !== undefined &&\n result.policyAllowed &&\n requiresHumanApproval(result) &&\n result.stepUpApproval?.pollUrl\n ) {\n const outcome = await awaitStepUpApproval(config, result.stepUpApproval);\n res.setHeader('X-Astra-Step-Up', outcome);\n if (outcome === 'approved') {\n result = await verify(config, verificationRequest, { bypassCache: true });\n req.agentVerification = result;\n sessionId = (result as EnhancedVerificationResult).sessionId;\n } else {\n result.stepUpOutcome = outcome;\n }\n }\n\n // v2.3.9 (defect #30): denied verifications short-circuit BEFORE the\n // gate-level comparison. See express.ts for the full rationale —\n // mirroring it here so the MCP path inherits the same belt-and-braces\n // protection against access-level/help-payload conflation.\n // 4.5.0: gate on the SERVER decision only (`policyAllowed`) — anonymous\n // callers allowed by an audit/allow endpoint policy are honestly\n // identity-unverified; see express.ts.\n if (!result.policyAllowed) {\n if (shouldRecordDecisions && sessionId) {\n recordDecision(config, sessionId, 'denied', result.denialReasons?.[0]).catch(() => {});\n }\n dedupeFailures(result);\n onDenied(result, req, res);\n return;\n }\n\n // F2 direct-path fail-closed: a step-up / requires-approval outcome\n // (policyAllowed:true, allowed-but-escalate) cannot be completed\n // autonomously — deny and surface stepUpApproval polling info.\n if (requiresHumanApproval(result)) {\n annotateApprovalRequired(result);\n if (shouldRecordDecisions && sessionId) {\n recordDecision(config, sessionId, 'denied', result.denialReasons?.[0]).catch(() => {});\n }\n dedupeFailures(result);\n onDenied(result, req, res);\n return;\n }\n\n // Round-13 (R13-5): evaluation-without-enforcement parity with the\n // express F9 adapter. When the route is `none` but we ran\n // verify-access because `evaluateAlwaysIfCredentialed: true` AND\n // credentials were present, skip the gates and call next() — the\n // result is on `req.agentVerification` for the MCP handler to\n // render tier-aware responses (e.g. anonymous vs verified\n // tool-listing on the same `/mcp` endpoint).\n if (!shouldEnforce) {\n if (config.setPassThroughHeader) {\n res.setHeader('X-Astra-Gateway-Mode', 'enforced');\n res.setHeader('X-Astra-Gateway-Reason', 'evaluated-not-enforced');\n }\n if (shouldRecordDecisions && sessionId) {\n recordDecision(config, sessionId, 'granted').catch(() => {});\n }\n return next();\n }\n\n // The access decision is the server's (identityVerified / policyAllowed,\n // denied above) — resting on a verified ID + runtime challenge + PDLSS\n // match + trust score. The per-tool gate only decides WHETHER a tool is\n // enforced at all (`shouldEnforce`, above); there is no client-side tier\n // veto (the access-level band was removed in SDK 5.0.0).\n\n // ── Tag the outbound response with X-Astra-Verified-Hop ────────────\n // Inner-hop REST endpoints called by this tool can read the header\n // and skip a duplicate verify-access. We set it on the response so\n // downstream proxies see it; tools that perform fetch() to an inner\n // hop should also forward it themselves (the MCP server framework\n // controls outbound calls — out of scope here).\n if (effectiveAstraId) {\n res.setHeader(\n MCP_VERIFIED_HOP_HEADER,\n serializeVerifiedHop({\n astraId: effectiveAstraId,\n ...(sessionId ? { sessionId } : {}),\n checkedAt: Date.now(),\n })\n );\n }\n\n if (shouldRecordDecisions && sessionId) {\n recordDecision(config, sessionId, 'granted').catch(() => {});\n }\n // v2.3.8 (defect #26): relay X-Astra-Unverified-Warning when the\n // server returned an audit-mode advisory.\n const enhancedResult = result as EnhancedVerificationResult;\n if (enhancedResult.warningHeader) {\n res.setHeader(enhancedResult.warningHeader.name, enhancedResult.warningHeader.value);\n }\n next();\n } catch (error) {\n // Audit F-A1-06: shadow-mode logging for fail-closed default migration.\n // Mirror of the express adapter pattern. Default 'open' for backward\n // compat in SDK 2.4.13; shadow logs let merchants grep correlationId\n // for impact analysis before the default flips in a follow-up release.\n const errorClass = error instanceof Error ? error.constructor.name : typeof error;\n const correlationId =\n (req.headers['x-request-id'] as string | undefined) ||\n (req.headers['x-correlation-id'] as string | undefined) ||\n `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n // eslint-disable-next-line no-console\n console.error('[VerificationGateway/MCP] Middleware error:', error);\n // eslint-disable-next-line no-console\n console.warn(\n `[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? 'unknown'} correlationId=${correlationId}`\n );\n\n if (failOnError === 'closed') {\n const result: VerificationResult = {\n identityVerified: false,\n policyAllowed: false,\n denialReasons: [`MCP middleware internal error: ${errorClass}`],\n failures: [\n {\n dimension: 'middleware.internal_error',\n message: `Middleware threw ${errorClass} — failing closed`,\n },\n ],\n verifiedAt: new Date(),\n correlationId,\n };\n const catchUrls = config.apiBaseUrl\n ? await getWellKnownUrls(config.apiBaseUrl).catch(() => undefined)\n : undefined;\n if (catchUrls) {\n result.guidance = {\n message: `Middleware threw ${errorClass} — failing closed`,\n registrationUrl: catchUrls.registrationUrl,\n documentationUrl: catchUrls.documentationUrl,\n };\n }\n dedupeFailures(result);\n return onDenied(result, req, res);\n }\n\n next();\n }\n };\n}\n\n// Re-export the helpers so consumers can mix-and-match (e.g. read the\n// verified-hop header themselves on a custom inner-hop handler).\nexport {\n parseMcpJsonRpc,\n mcpToPdlss,\n mcpDefaultEnforced,\n serializeVerifiedHop,\n parseVerifiedHop,\n isVerifiedHopValidFor,\n MCP_VERIFIED_HOP_HEADER,\n};\nexport type { ParsedMcpRequest } from '../transport/mcp-server';\n"],"mappings":"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","names":["has","result"]}
|
|
1
|
+
{"version":3,"sources":["../../src/version.ts","../../src/http.ts","../../src/trust-levels.ts","../../src/well-known.ts","../../src/verify.ts","../../src/metadata-capture.ts","../../src/adapters/approval-gate.ts","../../src/adapters/stepup-poll.ts","../../src/verified-hop.ts","../../src/transport/mcp-server.ts","../../src/adapters/mcp.ts"],"sourcesContent":["/**\n * Single source-of-truth for the SDK's\n * package version emitted on verify-access bodies (and any future\n * telemetry). Bumped alongside `package.json#version` on every release.\n *\n * Why a constant rather than `import pkg from '../package.json'`:\n * - `tsconfig.json` sets `rootDir: ./src`; importing the sibling\n * package.json fails the build with \"outside rootDir\".\n * - Build-time string replacement (tsup `define`, esbuild banner, etc.)\n * adds toolchain coupling for a trivial gain.\n * - Embedded readonly constant works in every environment (Node, browser,\n * bundlers, Deno) without runtime fs / network access.\n *\n * Release discipline: a CI lint can grep `package.json#version` against\n * this constant if the two ever diverge in the wild. A manual bump\n * is fine — bumping both in the release-ceremony commit keeps them\n * lockstep.\n */\nexport const SDK_VERSION = '5.6.0';\n","/**\n * Shared outbound-HTTP wrapper — every request the SDK makes to the\n * AstraSync backend (or anywhere else) goes through `sdkFetch` so it\n * carries an identifying User-Agent.\n *\n * Why: Node's global fetch defaults to `user-agent: node`, which is\n * indistinguishable from every other Node client on the wire. Visit\n * Intelligence observed our own SDK/beacon traffic as anonymous \"node\"\n * visits. Self-identification is the platform's own medicine.\n *\n * Browser note: some browsers filter the User-Agent request header\n * silently (never throwing), so this is a no-op there — which is correct:\n * in a browser the page's real UA is the honest identity.\n */\n\nimport { SDK_VERSION } from './version';\n\nexport const SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;\n\n/**\n * Add the SDK User-Agent without changing the SHAPE of the caller's headers\n * (plain records stay plain records) — interceptors, adapters, and tests\n * read `init.headers` as a record, and `Headers` would also lowercase names.\n */\nfunction withUserAgent(initHeaders: HeadersInit | undefined): HeadersInit {\n if (!initHeaders) return { 'user-agent': SDK_USER_AGENT };\n if (initHeaders instanceof Headers) {\n const copy = new Headers(initHeaders);\n if (!copy.has('user-agent')) copy.set('user-agent', SDK_USER_AGENT);\n return copy;\n }\n if (Array.isArray(initHeaders)) {\n const has = initHeaders.some(([name]) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : [...initHeaders, ['user-agent', SDK_USER_AGENT]];\n }\n const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : { 'user-agent': SDK_USER_AGENT, ...initHeaders };\n}\n\nexport const sdkFetch: typeof fetch = (input, init) =>\n fetch(input, { ...init, headers: withUserAgent(init?.headers) });\n","/**\n * AstraSync Universal Verification Gateway — Trust Level helpers.\n *\n * SDK 5.0.0: the `accessLevel` band (`none`/`restricted`/`read-only`/`standard`/\n * `full`/`internal`) was removed entirely — it never gated the access decision\n * (the server does, from a verified ID + runtime challenge + PDLSS match + trust\n * score). What remains here is the coarse trust-level badge derived from the\n * numeric trust score, used only for display.\n */\n\nimport type { TrustLevel } from './types';\n\n/**\n * Trust level score ranges\n */\nexport const TRUST_LEVEL_RANGES: Record<TrustLevel, { min: number; max: number }> = {\n BRONZE: { min: 0, max: 39 },\n SILVER: { min: 40, max: 59 },\n GOLD: { min: 60, max: 79 },\n PLATINUM: { min: 80, max: 100 },\n};\n\n/**\n * Determine trust level from score\n */\nexport function getTrustLevel(score: number): TrustLevel {\n if (score >= 80) return 'PLATINUM';\n if (score >= 60) return 'GOLD';\n if (score >= 40) return 'SILVER';\n return 'BRONZE';\n}\n","import { sdkFetch } from './http';\n/**\n * SDK-side discovery of canonical platform URLs via `/.well-known/agentic-commerce`.\n *\n * Fire-and-forget pre-fetch at middleware creation; first verify() awaits\n * the in-flight promise if it hasn't resolved. 60-minute TTL with\n * stale-while-revalidate background refresh.\n */\n\nexport interface WellKnownAgenticCommerce {\n registrationUrl: string;\n documentationUrl: string;\n verifyAccessUrl: string;\n /** 5.1.0: hosted AstraSync MCP bridge endpoint (absent on self-hosted backends). */\n mcpEndpoint?: string;\n /** 5.1.0: bridge connector-discovery doc (absent on self-hosted backends). */\n mcpDiscoveryUrl?: string;\n}\n\ninterface CacheEntry {\n data: WellKnownAgenticCommerce;\n fetchedAt: number;\n}\n\nconst CACHE_TTL_MS = 60 * 60 * 1000; // 60 minutes\n\nconst cache = new Map<string, CacheEntry>();\nconst inflight = new Map<string, Promise<WellKnownAgenticCommerce>>();\n\nfunction wellKnownUrl(apiBaseUrl: string): string {\n // apiBaseUrl is typically 'https://astrasync.ai/api' — strip the /api suffix\n // to get the platform root, then append /.well-known/agentic-commerce.\n const base = apiBaseUrl.replace(/\\/api\\/?$/, '');\n return `${base}/.well-known/agentic-commerce`;\n}\n\nasync function fetchWellKnown(apiBaseUrl: string): Promise<WellKnownAgenticCommerce> {\n const url = wellKnownUrl(apiBaseUrl);\n const response = await sdkFetch(url, {\n method: 'GET',\n headers: { Accept: 'application/json' },\n signal: AbortSignal.timeout(5000),\n });\n if (!response.ok) {\n throw new Error(\n `AstraSync platform must expose /.well-known/agentic-commerce; ` +\n `got ${response.status} from ${url}. SDK cannot initialise without it.`\n );\n }\n const data = (await response.json()) as Record<string, unknown>;\n if (!data.registrationUrl || !data.documentationUrl || !data.verifyAccessUrl) {\n throw new Error(\n `/.well-known/agentic-commerce response missing required fields ` +\n `(registrationUrl, documentationUrl, verifyAccessUrl).`\n );\n }\n return data as unknown as WellKnownAgenticCommerce;\n}\n\n/**\n * Start a background fetch. Returns the promise for callers that need\n * to await it (first verify() call).\n */\nexport function prefetchWellKnown(apiBaseUrl: string): Promise<WellKnownAgenticCommerce> {\n const existing = inflight.get(apiBaseUrl);\n if (existing) return existing;\n\n const promise = fetchWellKnown(apiBaseUrl)\n .then((data) => {\n cache.set(apiBaseUrl, { data, fetchedAt: Date.now() });\n inflight.delete(apiBaseUrl);\n return data;\n })\n .catch((err) => {\n inflight.delete(apiBaseUrl);\n throw err;\n });\n\n inflight.set(apiBaseUrl, promise);\n return promise;\n}\n\n/**\n * Get cached well-known URLs. If stale, triggers a background refresh\n * and returns stale data (stale-while-revalidate). If no cache exists,\n * awaits the in-flight fetch or starts a new one.\n */\nexport async function getWellKnownUrls(apiBaseUrl: string): Promise<WellKnownAgenticCommerce> {\n const entry = cache.get(apiBaseUrl);\n\n if (entry) {\n if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {\n // Stale — trigger background refresh, return stale data\n prefetchWellKnown(apiBaseUrl).catch(() => {\n // Background refresh failed — stale data remains until next request\n });\n }\n return entry.data;\n }\n\n // No cache — must await\n const pending = inflight.get(apiBaseUrl);\n if (pending) return pending;\n\n return prefetchWellKnown(apiBaseUrl);\n}\n\n/**\n * Synchronous cache read — returns cached URLs or undefined.\n * Never triggers a fetch. Used by verify() to avoid extra HTTP calls\n * in the hot path; adapters are responsible for prefetching.\n */\nexport function getCachedWellKnownUrls(apiBaseUrl: string): WellKnownAgenticCommerce | undefined {\n return cache.get(apiBaseUrl)?.data;\n}\n\n/** Reset cache — for testing only. */\nexport function _resetWellKnownCache(): void {\n cache.clear();\n inflight.clear();\n}\n","/**\n * AstraSync Universal Verification Gateway - Core Verification Logic\n *\n * This module handles the core verification logic, calling the AstraSync API\n * and processing the response into a standardized VerificationResult.\n */\n\nimport { sdkFetch } from './http';\nimport type {\n GatewayConfig,\n AgentCredentials,\n VerificationRequest,\n VerificationResult,\n VerifiedAgent,\n VerifiedDeveloper,\n VerifiedOrganization,\n GuidanceInfo,\n EnhancedVerificationResult,\n TokenGuidance,\n RuntimeChallengeResult,\n StepUpApprovalInfo,\n SettlementOutcomeInfo,\n MerchantSettlementToken,\n FulfilmentInfo,\n SettlementArtifact,\n AttemptReport,\n} from './types';\nimport { getTrustLevel } from './trust-levels';\nimport type { ObservedMetadata } from './metadata-capture';\nimport { SDK_VERSION } from './version';\nimport { getCachedWellKnownUrls, type WellKnownAgenticCommerce } from './well-known';\n\n/**\n * Default configuration values\n *\n * apiBaseUrl matches the OpenAPI authoritative server (https://astrasync.ai/api\n * for prod, https://staging.astrasync.ai/api for staging). Always include the\n * /api path prefix when overriding — registration / docs URLs are derived by\n * stripping it.\n */\nconst DEFAULT_CONFIG: Partial<GatewayConfig> = {\n apiBaseUrl: 'https://astrasync.ai/api',\n // minTrustScore + minTrustScoreForFull deprecated in v2.3.0 — server decides.\n // cacheTtl deliberately unset. When undefined, cacheResult applies the 60s\n // default; the caller's explicit value is honoured for cacheable results.\n // Denials and step-up escalations are NEVER cached (3.6.0) regardless of\n // cacheTtl. Set cacheTtl: 0 to disable caching entirely.\n debug: false,\n};\n\n/**\n * Init self-test state. Fires once per process on first verify() call to warn\n * if apiBaseUrl is pointing at the wrong host (e.g. a marketing site that\n * 200s with text/html instead of the API).\n */\nlet initCheckPerformed = false;\n\n/** Test-only: re-arm the one-time init self-check. */\nexport function _resetInitCheck(): void {\n initCheckPerformed = false;\n}\n\n/** One-shot guard for v2.3.0 deprecation warning. */\nlet deprecationWarningShown = false;\n\nasync function performInitCheck(\n apiBaseUrl: string,\n debug?: boolean,\n strictInit?: boolean,\n apiKey?: string\n): Promise<void> {\n initCheckPerformed = true;\n try {\n const probeUrl = `${apiBaseUrl}/agents/verify-access`;\n // HEAD mirrors GET semantics (running the full request pipeline without a\n // body) so the response carries the same content-type the marketing 404\n // would return. OPTIONS often gets short-circuited by CORS-preflight\n // handlers and returns no content-type, defeating the check.\n const response = await sdkFetch(probeUrl, { method: 'HEAD' });\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.startsWith('text/html')) {\n const message =\n `[VerificationGateway] apiBaseUrl '${apiBaseUrl}' returned HTML (content-type: ${contentType}). ` +\n `This usually means apiBaseUrl is pointing at a marketing site instead of the API. ` +\n `Expected: 'https://astrasync.ai/api' (prod) or 'https://staging.astrasync.ai/api' (staging).`;\n // Audit F-A6-33: when strictInit is set, throw on misconfig instead\n // of warning + continuing. Recurring footgun per\n // `[[feedback_astrasync_api_url_canonical]]` — silent warn lets\n // misconfigured prod deploys ship with all verify-access calls\n // silently failing.\n if (strictInit) {\n throw new Error(`${message} (strictInit=true)`);\n }\n console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);\n } else if (debug) {\n console.log(\n `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`\n );\n }\n\n // 4.0.0: key liveness probe. A dead API key otherwise surfaces only\n // per-request as inbound agents get turned away — the operator should\n // learn at deploy time. 404 = older backend without self-check: silent.\n if (apiKey) {\n const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check`, {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n if (selfCheck.status === 401) {\n const body = (await selfCheck.json().catch(() => ({}))) as {\n code?: string;\n message?: string;\n remediation?: { manageUrl?: string };\n };\n const message =\n `[VerificationGateway] the configured AstraSync API key was REJECTED at startup ` +\n `(${body.code ?? 'api_key_invalid'}: ${body.message ?? 'invalid key'}). Every inbound agent ` +\n `will be turned away until the key is rotated` +\n (body.remediation?.manageUrl ? ` at ${body.remediation.manageUrl}` : '') +\n `.`;\n if (strictInit) {\n throw new Error(`${message} (strictInit=true)`);\n }\n console.error(message);\n } else if (selfCheck.ok) {\n const body = (await selfCheck.json().catch(() => ({}))) as {\n daysToExpiry?: number | null;\n };\n if (typeof body.daysToExpiry === 'number' && body.daysToExpiry <= 7) {\n console.warn(\n `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) — ` +\n `rotate it at Settings → Developer Tools before integrations start failing.`\n );\n } else if (debug) {\n console.log('[VerificationGateway] API key self-check passed');\n }\n }\n }\n } catch (err) {\n if (strictInit) {\n // strict mode propagates init failures so callers get a clear,\n // synchronous \"your config is broken\" signal at startup.\n throw err;\n }\n if (debug) {\n console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);\n }\n }\n}\n\n/**\n * Simple in-memory cache for verification results\n */\nconst verificationCache = new Map<string, { result: VerificationResult; expiresAt: number }>();\n\n/**\n * Generate cache key from the full request shape.\n *\n * Historically the key was credentials-only (`astraId-apiKey-jwt`),\n * which silently returned the cached result of a prior call even when the\n * subsequent call's purpose/action/counterparty/etc. differed. That was a\n * security gap — a warm cache could serve an allow verdict for a purpose the\n * agent hadn't actually been authorised for. Key now includes every field\n * that affects the backend verdict.\n *\n * MAINTAIN: when adding a field to VerificationRequest that affects the\n * backend verdict, add it to this key. Fields that don't affect verdict\n * (e.g. requestId, timestamps, sdkVersion) don't belong here — including\n * them would needlessly thrash the cache for repeated identical requests.\n *\n * 3.12.0 (Trust Record): `attemptId` and `considerationSet` are\n * deliberately EXCLUDED — like `callerMetadata.observedMetadata` they are\n * observational pass-through that never affects the verdict, and keying on\n * a per-attempt id would defeat verdict caching entirely. Cache hits get\n * their attemptId rewritten in verify() instead (never leak a prior\n * attempt's id from a cached result).\n */\nfunction getCacheKey(request: VerificationRequest, counterpartyId?: string): string {\n const c = request.credentials;\n return [\n c.astraId || '',\n c.apiKey || '',\n c.jwt || '',\n request.purpose || '',\n request.action || '',\n request.resourceType || '',\n request.resource || '',\n request.jurisdiction || '',\n request.transactionValue ?? '',\n request.currency || '',\n // SECURITY (cross-merchant cache leak): the merchant identity is sent via\n // `config.counterpartyId`, NOT on the request, so it was previously absent\n // from the key — two verifies for the SAME agent/purpose/action/value but\n // DIFFERENT merchants collided, and a grant at a permissive merchant (low\n // trust floor) was served for a stricter one. Same bug class as the\n // duration omission (F-A1-07). counterpartyId affects the backend verdict\n // (trust floor / per-route policy), so it MUST key the cache.\n counterpartyId || '',\n request.counterpartyUrl || '',\n request.counterpartyType || '',\n request.isSubAgentRequest ? '1' : '0',\n request.parentAgentId || '',\n request.subAgentDepth ?? '',\n // Audit F-A1-07: previously-missing dimensions that DO affect the\n // backend verdict. Without these, two requests with different\n // durations (e.g. 60s vs 86400s) collided on the same cache key and\n // the shorter-duration allow served the longer-duration request.\n request.durationRequired ?? '',\n request.invocationProtocol || '',\n request.enableRuntimeChallenge ? '1' : '0',\n // callerMetadata fields contribute to risk model; include the ones\n // backend reads. sourceIp/userAgent/forwardedFor change per-request\n // so their inclusion effectively forces a re-check for any varying\n // client (the right behavior — IP-driven anomaly scoring shouldn't\n // be cached across IPs).\n request.callerMetadata?.sourceIp || '',\n request.callerMetadata?.userAgent || '',\n request.callerMetadata?.forwardedFor || '',\n request.callerMetadata?.agentCardUrl || '',\n // UAT2 Bug B (5.4.1): the commerce phase and cart identity DO affect the\n // backend verdict — the confirm leg is the ONLY leg that may execute\n // first-party settlement. Without these, a confirm within the cache TTL of\n // its start_checkout quote was served the cached quote-leg grant (no\n // settlementOutcome) and never reached the backend. Same F-A1-07 bug class.\n request.commercePhase || '',\n request.checkoutSessionId || '',\n ].join('|');\n}\n\n/**\n * Check if cached result is still valid\n */\nfunction getCachedResult(\n request: VerificationRequest,\n counterpartyId?: string\n): VerificationResult | null {\n const key = getCacheKey(request, counterpartyId);\n const cached = verificationCache.get(key);\n\n if (cached && cached.expiresAt > Date.now()) {\n return cached.result;\n }\n\n if (cached) {\n verificationCache.delete(key);\n }\n\n return null;\n}\n\n/**\n * Cache a verification result.\n *\n * 3.6.0: uniform 60s default. The earlier step-up TTL split (300s for\n * step-up verdicts) is gone — step-up verdicts are no longer cached AT ALL\n * (see the write predicate in verify()): a cached step_up_required result\n * carries a pollUrl that goes stale (consumed/expired) and, worse, keeps\n * denying retries that the owner has already approved. When `configuredTtl`\n * is set (truthy positive number), it's honoured for cacheable results.\n */\nconst DEFAULT_CACHE_TTL_SECONDS = 60;\n\nfunction cacheResult(\n request: VerificationRequest,\n result: VerificationResult,\n configuredTtl: number | undefined,\n counterpartyId?: string\n): void {\n const ttlSeconds = configuredTtl && configuredTtl > 0 ? configuredTtl : DEFAULT_CACHE_TTL_SECONDS;\n const key = getCacheKey(request, counterpartyId);\n verificationCache.set(key, {\n result,\n expiresAt: Date.now() + ttlSeconds * 1000,\n });\n}\n\n/**\n * Clear the verification cache\n */\nexport function clearCache(): void {\n verificationCache.clear();\n}\n\n/**\n * Extract agent credentials from various sources\n */\nexport function extractCredentials(\n headers: Record<string, string | string[] | undefined>,\n query?: Record<string, string | undefined>\n): AgentCredentials {\n const credentials: AgentCredentials = {};\n\n // Audit F-A1-12: ASTRA-ID shape validation. Accept only the canonical\n // pattern (ASTRA-/ASTRAE- prefix + 1-64 url-safe chars) to prevent CRLF\n // header injection or downstream interpolation surprises. Reject otherwise.\n const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;\n\n // Check for ASTRA-ID in headers (case-insensitive). Accepts the historical\n // X-Astra-Id name plus the X-Astra-AgentId / x-astra-agent-id alias the\n // partner asked for in #9a — both surface the same field.\n const astraIdHeader =\n headers['x-astra-id'] ||\n headers['X-Astra-Id'] ||\n headers['X-ASTRA-ID'] ||\n headers['x-astra-agentid'] ||\n headers['X-Astra-AgentId'] ||\n headers['x-astra-agent-id'] ||\n headers['X-Astra-Agent-Id'] ||\n headers['X-ASTRA-AGENT-ID'];\n if (astraIdHeader) {\n // Audit F-A1-11: guard array-confusion. If multiple X-Astra-Id headers\n // were sent (Express collapses to comma-joined string on most paths),\n // take only the first canonical-shaped one.\n const raw = Array.isArray(astraIdHeader)\n ? astraIdHeader[0]\n : typeof astraIdHeader === 'string'\n ? astraIdHeader\n : undefined;\n if (typeof raw === 'string' && ASTRA_ID_PATTERN.test(raw)) {\n credentials.astraId = raw;\n }\n }\n\n // Check for API key in headers\n const apiKeyHeader = headers['x-api-key'] || headers['X-Api-Key'] || headers['X-API-KEY'];\n if (apiKeyHeader) {\n credentials.apiKey = Array.isArray(apiKeyHeader) ? apiKeyHeader[0] : apiKeyHeader;\n }\n\n // Check Authorization header for Bearer token.\n // Audit F-A1-11: defensive type guard — Array.isArray gives a non-string,\n // and Express path quirks can produce undefined despite the truthy check.\n const authHeader = headers['authorization'] || headers['Authorization'];\n if (authHeader) {\n const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;\n if (typeof authValue === 'string') {\n credentials.authorizationHeader = authValue;\n if (authValue.startsWith('Bearer ')) {\n credentials.jwt = authValue.slice(7);\n }\n }\n }\n\n // Check query parameters as fallback\n if (query) {\n if (query.astraId && !credentials.astraId) {\n credentials.astraId = query.astraId;\n }\n if (query.apiKey && !credentials.apiKey) {\n credentials.apiKey = query.apiKey;\n }\n }\n\n return credentials;\n}\n\n/**\n * Check if credentials are present\n */\nexport function hasCredentials(credentials: AgentCredentials): boolean {\n return !!(credentials.astraId || credentials.apiKey || credentials.jwt);\n}\n\n/**\n * Source of a synthesised guidance response. The sources are split — the\n * `'no_credentials'` shape is the original (caller has no AstraSync\n * credentials, suggest registration). The `'api_error'` shape is for when\n * the verify-access HTTP call itself failed (5xx, network, etc.) — DON'T\n * tell a verified-but-currently-blocked partner to \"register your agent\".\n *\n * 4.0.0 adds `'merchant_misconfig'`: the backend rejected THIS INTEGRATION'S\n * own API key (401 api_key_revoked/expired/invalid). Neither the agent's\n * fault nor a platform outage — retrying won't help, and the inbound agent\n * deserves to know the site operator's integration is broken (the\n * gadget-emporium failure mode: a rotated key silently 401ing every\n * credentialed agent for days).\n */\ntype GuidanceSource = 'no_credentials' | 'api_error' | 'merchant_misconfig';\n\n/**\n * MCP-connector steering (5.1.0). Present only when the backend's well-known\n * doc advertises a hosted bridge. The copy must directly counter the observed\n * model refusal (\"go register manually on the dashboard\"): registration via\n * the connector is the agent's own next action, with the human approval built\n * into the flow.\n */\nfunction mcpGuidance(urls?: WellKnownAgenticCommerce): GuidanceInfo['mcp'] {\n if (!urls?.mcpEndpoint) return undefined;\n return {\n endpoint: urls.mcpEndpoint,\n discoveryUrl: urls.mcpDiscoveryUrl,\n message:\n 'If you are operating inside an MCP-capable host (Claude, ChatGPT, Cursor, or any MCP client), ' +\n 'add the AstraSync MCP connector instead of integrating these URLs by hand: registration through ' +\n 'the connector is KEYLESS and HUMAN-APPROVED — your human approves in an in-chat card, and you ' +\n 'never see, hold, or transmit an API key, password, or private key.',\n };\n}\n\n/**\n * Create guidance response for unverified agents or for API-error fallback.\n *\n * The source is split so the `register your agent` template\n * doesn't fire on transient backend failures. Also threads `correlationId`\n * through so adapter `onDenied` handlers can surface it on the merchant's\n * response body for log correlation.\n */\nfunction createGuidanceResponse(\n _config: GatewayConfig,\n reason?: string,\n options: {\n source?: GuidanceSource;\n correlationId?: string;\n /** 3.12.0 (Trust Record): server-echoed attempt handle, threaded like correlationId. */\n attemptId?: string;\n urls?: WellKnownAgenticCommerce;\n /** 4.0.0: backend-provided remediation URLs for the merchant (misconfig source). */\n remediation?: { docsUrl?: string; manageUrl?: string };\n } = {}\n): VerificationResult {\n const source = options.source ?? 'no_credentials';\n const isApiError = source === 'api_error';\n const isMisconfig = source === 'merchant_misconfig';\n const urls = options.urls;\n\n const guidance: GuidanceInfo = isMisconfig\n ? {\n message:\n \"This site's AstraSync integration is misconfigured (its API key was revoked, expired, or rotated). \" +\n 'Your identity and the AstraSync platform are fine — contact the site operator. Retrying will not help.',\n registrationUrl: urls?.registrationUrl ?? '',\n documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? '',\n steps: [\n 'Report the failure to the site operator — their AstraSync API key needs rotating',\n 'Do not retry: the failure is deterministic until the operator updates their key',\n ],\n }\n : isApiError\n ? {\n message:\n 'Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.',\n registrationUrl: urls?.registrationUrl ?? '',\n documentationUrl: urls?.documentationUrl ?? '',\n steps: [\n 'Retry the request with exponential backoff',\n 'If failures persist, share the correlationId with support',\n ],\n }\n : {\n message:\n 'This service verifies AI agents before granting access. Please register your agent with AstraSync.',\n registrationUrl: urls?.registrationUrl ?? '',\n documentationUrl: urls?.documentationUrl ?? '',\n steps: [\n // MCP steering leads when the hosted bridge is advertised — the\n // connector is the agent-actionable path (keyless, human-approved).\n ...(urls?.mcpEndpoint\n ? [\n `Fastest path (inside an MCP host): add the AstraSync MCP connector at ${urls.mcpEndpoint} — keyless, human-approved registration`,\n ]\n : []),\n 'Register for an AstraSync account',\n 'Create and register your agent',\n 'Add your ASTRA-ID to request headers',\n 'Retry your request',\n ],\n ...(mcpGuidance(urls) && { mcp: mcpGuidance(urls) }),\n };\n\n return {\n // Round-18 G4: createGuidanceResponse fires for unverified-agent path or\n // API-error fallback. Identity is not verified (no agent resolved);\n // policy is not evaluated (we never reached the gate).\n identityVerified: false,\n policyAllowed: false,\n guidance,\n denialReasons: reason ? [reason] : ['No valid agent credentials provided'],\n // Round-10 (#47, O5): on API-error fallback, surface a typed failure so\n // partners (and their custom onDenied handlers) can branch on\n // dimension. Without this, the synthesised stub was indistinguishable\n // from a real policy deny.\n failures: isMisconfig\n ? [\n {\n dimension: 'verify_access.merchant_misconfigured',\n message: reason ?? 'The integration API key was rejected by AstraSync',\n guidance: guidance.message,\n },\n ]\n : isApiError\n ? [\n {\n dimension: 'verify_access.api_error',\n message: reason ?? 'Verification temporarily unavailable',\n guidance: guidance.message,\n },\n ]\n : undefined,\n // 4.0.0: typed marker so adapters render the misconfig envelope (503 +\n // MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.\n ...(isMisconfig && { misconfigured: true }),\n // 4.6.0: a transient api_error is retryable — mark it so consumers surface\n // \"temporarily unavailable, retry\" (recommendation 'error') instead of\n // coercing the absent recommendation to a policy 'deny'. Misconfig is\n // deterministic and intentionally NOT marked retryable.\n ...(isApiError && { retryable: true }),\n correlationId: options.correlationId,\n attemptId: options.attemptId,\n verifiedAt: new Date(),\n };\n}\n\n/**\n * Call the AstraSync verify-access API\n */\nasync function callVerifyAccessAPI(\n config: GatewayConfig,\n request: VerificationRequest\n): Promise<{\n success: boolean;\n access?: {\n allowed: boolean;\n reason?: string;\n /**\n * Aggregated denial failures (v2.9.8+). Empty / absent when allowed.\n * Each entry is `{ dimension, message, guidance? }` — see\n * `AccessFailure` for the contract.\n */\n failures?: Array<{ dimension: string; message: string; guidance?: string }>;\n requiresStepUp?: boolean;\n requiresApproval?: boolean;\n appliedPolicy?: {\n boundaryName: string;\n policyVersion: string;\n };\n counterparty?: {\n id: string;\n name: string;\n trustScoreRequirement: number;\n };\n };\n agent?: {\n kyaAgentId: string;\n astraId: string;\n name: string;\n trustScore: number;\n trustLevel: string;\n agentStatus: string;\n blockchainStatus: string;\n };\n developer?: {\n kyaOwnerId: string;\n fullName: string;\n email: string;\n identityVerified: boolean;\n trustScore: number;\n };\n organization?: {\n name: string;\n verified: boolean;\n trustScore: number;\n };\n /**\n * Structured explanation of the verification decision. Tells the merchant\n * WHY (id verified? challenge passed? request within PDLSS? trust score?)\n * without exposing thresholds, scope lists, or other-tenant counterparty\n * membership. Empty `attestations` unless the endpoint's access policy\n * declared `required_attestations`.\n */\n verificationContext?: {\n idVerified: boolean;\n runtimeChallenge: {\n status: 'passed' | 'skipped' | 'failed' | 'timeout' | 'not_supported';\n checkedAt: string | null;\n };\n pdlssCheck: {\n result: 'within' | 'exceeded' | 'denied' | 'not_evaluated';\n purpose: 'approved' | 'denied';\n scope: 'approved' | 'denied';\n };\n dynamicTrustScore: number;\n attestations: Array<{\n type: string;\n status: 'passed' | 'failed';\n /** LMAX-POC C1: underlying check timestamp for merchant freshness gates. */\n checkedAt: string;\n validUntil?: string;\n proofType: 'reference' | 'zkp';\n proof: string;\n }>;\n };\n error?: string;\n /**\n * 4.0.0: the backend 401'd this integration's OWN API key (revoked /\n * expired / invalid) — deterministic merchant-side misconfig, not a\n * transient failure. `misconfigCode` carries the backend's api_key_* code\n * and `remediation` its operator-facing URLs when present.\n */\n misconfig?: boolean;\n misconfigCode?: string;\n remediation?: { docsUrl?: string; manageUrl?: string };\n /**\n * When the verify-access server response carries a\n * correlationId on an error envelope, propagate it so the SDK can thread\n * it through createGuidanceResponse → adapter onDenied → merchant body.\n */\n correlationId?: string;\n /**\n * 3.12.0 (Trust Record): the server echoes the attempt-chain\n * handle top-level on every response branch (error envelopes included).\n */\n attemptId?: string;\n}> {\n const { credentials, ...requestData } = request;\n\n // Build the request body. agentId is omitted when not provided so the\n // server treats it as an anonymous canonical-flow call (Branch A/B/C).\n const body: Record<string, unknown> = {\n ...(credentials.astraId && { agentId: credentials.astraId }),\n ...(requestData.purpose && { purpose: requestData.purpose }),\n };\n\n // Add optional fields\n if (requestData.action) body.action = requestData.action;\n if (requestData.resourceType) body.resourceType = requestData.resourceType;\n if (requestData.resource) body.resource = requestData.resource;\n if (requestData.jurisdiction) body.jurisdiction = requestData.jurisdiction;\n if (requestData.transactionValue) body.transactionValue = requestData.transactionValue;\n if (requestData.currency) body.currency = requestData.currency;\n if (requestData.isSubAgentRequest) body.isSubAgentRequest = requestData.isSubAgentRequest;\n if (requestData.parentAgentId) body.parentAgentId = requestData.parentAgentId;\n if (requestData.subAgentDepth !== undefined) body.subAgentDepth = requestData.subAgentDepth;\n // Handshake Protocol v10 additions\n if (requestData.enableRuntimeChallenge)\n body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;\n if (requestData.createSession) body.createSession = requestData.createSession;\n if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;\n if (requestData.counterpartyType) body.counterpartyType = requestData.counterpartyType;\n if (requestData.counterpartyUrl) body.counterpartyUrl = requestData.counterpartyUrl;\n if (config.counterpartyId) body.counterpartyId = config.counterpartyId;\n if (requestData.runtimeChallengeOptions)\n body.runtimeChallengeOptions = requestData.runtimeChallengeOptions;\n // Round-12 (F19): transport-vs-intent separation. MCP middleware sets\n // this to 'mcp'; non-MCP callers leave it unset.\n if (requestData.invocationProtocol) body.invocationProtocol = requestData.invocationProtocol;\n // 5.3.0 (astra-pay): checkout-leg discriminator — first-party settlement\n // fires only on 'confirm'; absent keeps pre-phase server behavior.\n if (requestData.commercePhase) body.commercePhase = requestData.commercePhase;\n // Per-cart idempotency key (checkout session id) forwarded on the confirm\n // leg so concurrent confirms for one cart charge at most once.\n if (requestData.checkoutSessionId) body.checkoutSessionId = requestData.checkoutSessionId;\n // Order-plane cart lines (receipts + fulfillment email) — informational.\n if (requestData.checkoutItems) body.checkoutItems = requestData.checkoutItems;\n // 5.6.0 (astra-pay): transit-only fulfilment contact — echoed back in\n // result.fulfilment for first-party confirms, never persisted server-side.\n if (requestData.buyerEmail) body.buyerEmail = requestData.buyerEmail;\n // 3.9.0 (§33.5 #1): raw commerce artifacts forwarded verbatim — the\n // backend's Step 0.5 pipeline does the cryptographic verification\n // server-side (adapters forward, never verify locally).\n if (requestData.commerceArtifacts) body.commerceArtifacts = requestData.commerceArtifacts;\n // 3.12.0 (Trust Record, §33.5 #3): attempt-chain handle + consideration set,\n // forwarded skip-when-undefined (observedMetadata precedent) — pure\n // observational pass-through; the server echoes attemptId on every branch.\n if (requestData.attemptId) body.attemptId = requestData.attemptId;\n if (requestData.considerationSet) body.considerationSet = requestData.considerationSet;\n\n // Round-13 (F14 closure / R13-7): emit the SDK package version on every\n // verify-access body so the backend can auto-populate `kya_counterparty.\n // sdk_version` for the calling endpoint. Body field (not User-Agent\n // header) is the canonical channel — works in Node, browser, behind\n // Cloudflare, and across SDK bundlers without environment-specific\n // header gymnastics. Backend's `validation.ts:verifyAccessSchema`\n // accepts the semver regex; the auto-pop logic is forward-only.\n body.sdkVersion = SDK_VERSION;\n\n // Forward caller metadata when present. Merges the legacy top-level\n // clientIp/userAgent into the nested block for backward compatibility.\n if (requestData.callerMetadata || requestData.clientIp || requestData.userAgent) {\n const meta = {\n ...(requestData.clientIp && { sourceIp: requestData.clientIp }),\n ...(requestData.userAgent && { userAgent: requestData.userAgent }),\n ...requestData.callerMetadata,\n };\n if (Object.keys(meta).length > 0) body.callerMetadata = meta;\n }\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...config.customHeaders,\n };\n\n // verify-access requires authentication. Use ONLY the merchant's\n // configured apiKey for outbound auth. Previously the caller's inbound\n // Authorization header took priority, which pivoted the verify-access\n // call to the caller's identity — letting an attacker who supplied\n // their own Authorization header trigger verify-access calls under\n // THEIR account and observe the resulting verdicts for arbitrary\n // lookups they triggered via the merchant. Audit F-A1-10.\n //\n // The caller-supplied JWT (if any) is already extracted to\n // credentials.jwt and travels in the verify-access request body so\n // backend can evaluate it as the agent's identity claim — separate from\n // the merchant-attribution auth on the outbound HTTPS call.\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n // Legacy header kept for compatibility with any middleware that reads it directly.\n headers['X-API-Key'] = config.apiKey;\n }\n\n try {\n // 4.0.0: hard timeout — without one, a backend that accepts the\n // connection but never answers hangs the merchant's middleware and every\n // inbound agent request behind it (observed live on a partner property).\n // Timeouts land on the catch path below → api_error (fail-closed),\n // NOT misconfig. The edge adapter races its own tighter withBudget.\n const response = await sdkFetch(`${config.apiBaseUrl}/agents/verify-access`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 10_000),\n });\n\n const data = await response.json();\n\n // v2.3.8 (defect #29): treat 410 Gone as a deterministic deactivated-endpoint\n // signal. Older SDKs may treat any non-2xx as transient and retry; v2.3.8+\n // surfaces it as a structured denial with `reason: 'endpoint_deactivated'`\n // so callers can distinguish \"endpoint gone\" from \"endpoint denied this\".\n if (response.status === 410) {\n return {\n success: true,\n // 3.12.0 (Trust Record): keep the server-echoed attempt handle even on\n // the synthesised 410 deny shape (echoed on every branch).\n attemptId: typeof data?.attemptId === 'string' ? data.attemptId : undefined,\n access: {\n allowed: false,\n reason: 'endpoint_deactivated',\n failures: [\n {\n dimension: 'endpoint.deactivated',\n message:\n typeof data?.message === 'string' ? data.message : 'Endpoint has been deactivated',\n guidance:\n typeof data?.guidance === 'string'\n ? data.guidance\n : 'Reactivate via POST /api/endpoints/{id}/reactivate, or update the URL on the calling agent.',\n },\n ],\n },\n };\n }\n\n if (!response.ok) {\n // 4.0.0: a 401 from verify-access can ONLY mean the backend rejected\n // THIS INTEGRATION'S own API key — agent denials come back as 200 with\n // access.allowed=false. Surface it as merchant-misconfig, not a\n // transient api_error: it is deterministic until the operator rotates\n // the key, and the \"retry with backoff\" template actively misleads.\n // Backends ≥ this round also send code: api_key_* + remediation URLs.\n if (response.status === 401) {\n return {\n success: false,\n error: data.message || data.error || 'Integration API key rejected',\n misconfig: true,\n misconfigCode: typeof data?.code === 'string' ? data.code : undefined,\n remediation:\n data?.remediation && typeof data.remediation === 'object'\n ? (data.remediation as { docsUrl?: string; manageUrl?: string })\n : undefined,\n correlationId: typeof data?.correlationId === 'string' ? data.correlationId : undefined,\n attemptId: typeof data?.attemptId === 'string' ? data.attemptId : undefined,\n };\n }\n // Round-10 (#47, O5): propagate correlationId on the error path too if\n // the server happened to include one (some 5xx error envelopes carry\n // it). Lets the SDK pass it through to the adapter onDenied handler.\n return {\n success: false,\n error: data.message || data.error || `API returned ${response.status}`,\n correlationId: typeof data?.correlationId === 'string' ? data.correlationId : undefined,\n attemptId: typeof data?.attemptId === 'string' ? data.attemptId : undefined,\n };\n }\n\n return data;\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n return {\n success: false,\n error: `Failed to call verify-access API: ${message}`,\n };\n }\n}\n\n/**\n * Options for a single verify() call (as opposed to GatewayConfig, which is\n * per-gateway). Mirrors the Java SDK's `verify(request, bypassCache)`.\n */\nexport interface VerifyOptions {\n /**\n * Skip the cache READ for this call so the request is guaranteed to reach\n * the backend (the result is still written to the cache if cacheable).\n * Used by the step-up hold-and-poll retry: the re-verify after an approval\n * must hit the server to redeem the single-use approval.\n */\n bypassCache?: boolean;\n}\n\n/**\n * Main verification function\n */\nexport async function verify(\n config: GatewayConfig,\n request: VerificationRequest,\n options?: VerifyOptions\n): Promise<VerificationResult> {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n\n // v2.5.0: read well-known URLs from cache (prefetched by adapter at middleware creation).\n // Synchronous — never triggers a fetch. If cache is empty, guidance URLs are empty strings.\n const urls = mergedConfig.apiBaseUrl\n ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl)\n : undefined;\n\n // One-time init self-test — fire-and-forget by default, never blocks verify().\n // Audit F-A6-33: when GatewayConfig.strictInit is set, the init promise is\n // awaited and any failure throws synchronously so callers learn about\n // misconfigured apiBaseUrl on the first verify() call rather than silently\n // running with broken auth.\n if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {\n if (mergedConfig.strictInit) {\n await performInitCheck(\n mergedConfig.apiBaseUrl,\n mergedConfig.debug,\n true,\n mergedConfig.apiKey\n );\n } else {\n void performInitCheck(\n mergedConfig.apiBaseUrl,\n mergedConfig.debug,\n false,\n mergedConfig.apiKey\n );\n }\n }\n\n // Deprecation warning for v2.3.0 removed config fields. Fires once per process.\n if (\n !deprecationWarningShown &&\n (config.minTrustScore !== undefined || config.minTrustScoreForFull !== undefined)\n ) {\n deprecationWarningShown = true;\n console.warn(\n '[VerificationGateway] minTrustScore / minTrustScoreForFull are deprecated in v2.3.0 ' +\n 'and have no effect. Server is now the single source of truth for access decisions ' +\n '(verified ID + runtime challenge + PDLSS + trust score). To gate access ' +\n \"to an endpoint, configure the endpoint's trust_score_requirement server-side.\"\n );\n }\n\n // v2.3.0: anonymous traffic no longer short-circuits here. We forward the\n // request to the server with no agentId; the server applies the endpoint's\n // unverifiedAgentPolicy and returns advisory. createGuidanceResponse remains\n // as the offline fallback if the API itself fails (handled below).\n\n // Check cache first. Caching on unless caller explicitly sets cacheTtl: 0\n // to disable. Undefined falls through to the 60s default at cacheResult\n // write time. options.bypassCache skips the read only (hold-and-poll retry\n // must reach the backend to redeem its approval).\n if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache) {\n const cached = getCachedResult(request, mergedConfig.counterpartyId);\n if (cached) {\n if (mergedConfig.debug) {\n console.log('[VerificationGateway] Returning cached result');\n }\n // 3.12.0 (Trust Record, §33.5 #3): attemptId is excluded from the cache\n // key, so the stored result may carry a PRIOR attempt's id. Overwrite it\n // with this request's own attemptId — or drop it when this request has\n // none — a cached verdict must never leak another attempt's handle.\n // Shallow clone: never mutate the stored entry (a concurrent holder of\n // the same cached object may still be reading it).\n const hit: VerificationResult = { ...cached };\n if (request.attemptId) {\n hit.attemptId = request.attemptId;\n } else {\n delete hit.attemptId;\n }\n return hit;\n }\n }\n\n // Inject counterparty info from config if not already set in request\n const enrichedRequest = { ...request };\n if (!enrichedRequest.counterpartyUrl && mergedConfig.counterpartyUrl) {\n enrichedRequest.counterpartyUrl = mergedConfig.counterpartyUrl;\n }\n if (!enrichedRequest.counterpartyType && mergedConfig.counterpartyType) {\n enrichedRequest.counterpartyType = mergedConfig.counterpartyType;\n }\n\n // Call the API\n if (mergedConfig.debug) {\n console.log('[VerificationGateway] Calling verify-access API');\n }\n\n const apiResponse = await callVerifyAccessAPI(mergedConfig, enrichedRequest);\n\n // 3.6.0: the server emits a top-level denialReasons array on every deny\n // path (same strings as the failure messages). Non-empty → authoritative;\n // absent/empty → fall back to the client-side reconstructions below.\n const rawServerDenialReasons = (apiResponse as Record<string, unknown>).denialReasons;\n const serverDenialReasons =\n Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0\n ? (rawServerDenialReasons as string[])\n : undefined;\n\n // Handle API errors\n if (!apiResponse.success) {\n // Round-10 (#47, O5): distinguish API errors from missing-credentials.\n // The previous default tagged any failure with the \"register your\n // agent\" template, which is misleading to a verified partner whose\n // verify-access call hit a 500. The `api_error` source surfaces a\n // typed `verify_access.api_error` failure entry instead.\n // 4.0.0: a rejected merchant key gets its own source — deterministic\n // misconfig must not wear the \"temporarily unavailable, retry\" template.\n return createGuidanceResponse(mergedConfig, apiResponse.error, {\n source: apiResponse.misconfig ? 'merchant_misconfig' : 'api_error',\n correlationId: (apiResponse as { correlationId?: string }).correlationId,\n attemptId: (apiResponse as { attemptId?: string }).attemptId,\n remediation: apiResponse.remediation,\n urls,\n });\n }\n\n // Check access result\n if (!apiResponse.access?.allowed) {\n // v2.9.8 (defect M1): aggregated failures across every gate that\n // denied. Surface them on the result so the integrator can see every\n // blocker in one go instead of the previous fail-fast cascade.\n const aggregatedFailures = (apiResponse.access as Record<string, unknown> | undefined)\n ?.failures as Array<{ dimension: string; message: string; guidance?: string }> | undefined;\n // Round-18 G4: backend denied access (PDLSS or other gate); identity status\n // depends on whether the backend resolved the caller. Read from\n // verificationContext.idVerified; default false if absent (anonymous or\n // identity-fail paths land here too). policyAllowed is false by definition\n // in this branch (apiResponse.access.allowed === false).\n const idVerifiedFromBackend =\n (apiResponse.verificationContext as { idVerified?: boolean } | undefined)?.idVerified ===\n true;\n const result: EnhancedVerificationResult = {\n identityVerified: idVerifiedFromBackend,\n policyAllowed: false,\n // 3.6.0: the server now emits top-level denialReasons on every deny\n // path — prefer it verbatim; the failure-message reconstruction remains\n // as the fallback for older backends.\n denialReasons:\n serverDenialReasons ??\n (aggregatedFailures && aggregatedFailures.length > 0\n ? aggregatedFailures.map((f) => f.message)\n : apiResponse.access?.reason\n ? [apiResponse.access.reason]\n : ['Access denied']),\n failures: aggregatedFailures,\n requiresStepUp: apiResponse.access?.requiresStepUp,\n requiresApproval: apiResponse.access?.requiresApproval,\n guidance: {\n message: apiResponse.access?.reason || 'Access denied by PDLSS policy',\n registrationUrl: urls?.registrationUrl ?? '',\n documentationUrl: urls?.documentationUrl ?? '',\n // 5.1.0: anonymous callers land on this branch — steer MCP-hosted\n // agents to the connector (this is what the edge deny body surfaces).\n ...(mcpGuidance(urls) && { mcp: mcpGuidance(urls) }),\n },\n verifiedAt: new Date(),\n // Extract sessionId so decisions can be recorded for denials too\n sessionId: (apiResponse as Record<string, unknown>).sessionId as string | undefined,\n // Anonymous traffic has no session → correlationId is the per-attempt\n // linking key (the sessionId-equivalent for anonymous callers).\n correlationId: (apiResponse as Record<string, unknown>).correlationId as string | undefined,\n // 3.12.0 (Trust Record): server echoes the attempt-chain handle on\n // every branch — denials included (the funnel outlives one deny).\n attemptId: (apiResponse as Record<string, unknown>).attemptId as string | undefined,\n recommendation: (apiResponse as Record<string, unknown>)\n .recommendation as EnhancedVerificationResult['recommendation'],\n recommendationReasons: (apiResponse as Record<string, unknown>).recommendationReasons as\n | string[]\n | undefined,\n stepUpApproval: (apiResponse as Record<string, unknown>).stepUpApproval as\n | StepUpApprovalInfo\n | undefined,\n settlement: (apiResponse as Record<string, unknown>).settlement as\n | SettlementArtifact\n | undefined,\n settlementOutcome: (apiResponse as Record<string, unknown>).settlementOutcome as\n | SettlementOutcomeInfo\n | undefined,\n // 5.5.0 self-settlement: merchant-only material — passes through to the\n // MERCHANT caller only (bridge callers strip it before the agent plane).\n settlementToken: (apiResponse as Record<string, unknown>).settlementToken as\n | MerchantSettlementToken\n | undefined,\n // 5.6.0: fulfilment contact — merchant-only lane, same strip rule.\n fulfilment: (apiResponse as Record<string, unknown>).fulfilment as FulfilmentInfo | undefined,\n };\n\n return result;\n }\n\n // Build successful result\n const agent: VerifiedAgent | undefined = apiResponse.agent\n ? {\n astraId: apiResponse.agent.astraId,\n name: apiResponse.agent.name,\n trustScore: apiResponse.agent.trustScore,\n trustLevel: getTrustLevel(apiResponse.agent.trustScore),\n blockchainVerified: apiResponse.agent.blockchainStatus === 'verified',\n status: apiResponse.agent.agentStatus as VerifiedAgent['status'],\n }\n : undefined;\n\n const developer: VerifiedDeveloper | undefined = apiResponse.developer\n ? {\n astradId: apiResponse.developer.kyaOwnerId,\n name: apiResponse.developer.fullName,\n trustScore: apiResponse.developer.trustScore || 0,\n verified: apiResponse.developer.identityVerified,\n }\n : undefined;\n\n const organization: VerifiedOrganization | undefined = apiResponse.organization\n ? {\n name: apiResponse.organization.name,\n verified: apiResponse.organization.verified,\n trustScore: apiResponse.organization.trustScore,\n }\n : undefined;\n\n // Verification context — structured \"why\" the merchant gets in v2.2.4+.\n // Carries appliedPolicy (no UUIDs), pdlssCheck summary, dynamic trust score,\n // and policy-driven attestations. Replaces the old over-sharing `pdlss` block.\n const verificationContext = apiResponse.verificationContext;\n\n const result: EnhancedVerificationResult = {\n // 4.5.0 (probe F1): read idVerified from verificationContext with the\n // SAME absent⇒false default as the deny branch. The previous `!== false`\n // default manufactured `identityVerified: true` for anonymous\n // allow_full/allow_partial/audit responses (which allow access but omit\n // verificationContext entirely) — the \"anonymous caller labelled\n // verified\" defect. Every authenticated success response has carried\n // `verificationContext.idVerified: true` since v2.2.4, so a truth claim\n // this load-bearing must never be defaulted on.\n identityVerified:\n (apiResponse.verificationContext as { idVerified?: boolean } | undefined)?.idVerified ===\n true,\n policyAllowed: true,\n agent,\n developer,\n organization,\n appliedPolicy: apiResponse.access?.appliedPolicy,\n verificationContext,\n requiresStepUp: apiResponse.access?.requiresStepUp,\n requiresApproval: apiResponse.access?.requiresApproval,\n verifiedAt: new Date(),\n cacheTtl: mergedConfig.cacheTtl,\n // Handshake Protocol v10 enhanced fields (present when backend returns them)\n sessionId: (apiResponse as Record<string, unknown>).sessionId as string | undefined,\n // v2.3.10 (defect #34, round-4): anonymous responses surface correlationId\n // (no session row exists for unverified callers).\n correlationId: (apiResponse as Record<string, unknown>).correlationId as string | undefined,\n // 3.12.0 (Trust Record): server-echoed attempt-chain handle.\n attemptId: (apiResponse as Record<string, unknown>).attemptId as string | undefined,\n runtimeChallenge: (apiResponse as Record<string, unknown>).runtimeChallenge as\n | RuntimeChallengeResult\n | undefined,\n tokenGuidance: (apiResponse as Record<string, unknown>).tokenGuidance as\n | TokenGuidance\n | undefined,\n recommendation: (apiResponse as Record<string, unknown>)\n .recommendation as EnhancedVerificationResult['recommendation'],\n recommendationReasons: (apiResponse as Record<string, unknown>).recommendationReasons as\n | string[]\n | undefined,\n warningHeader: (apiResponse as Record<string, unknown>).warningHeader as\n | { name: string; value: string }\n | undefined,\n stepUpApproval: (apiResponse as Record<string, unknown>).stepUpApproval as\n | StepUpApprovalInfo\n | undefined,\n settlement: (apiResponse as Record<string, unknown>).settlement as\n | SettlementArtifact\n | undefined,\n settlementOutcome: (apiResponse as Record<string, unknown>).settlementOutcome as\n | SettlementOutcomeInfo\n | undefined,\n settlementToken: (apiResponse as Record<string, unknown>).settlementToken as\n | MerchantSettlementToken\n | undefined,\n // 5.6.0: fulfilment contact — merchant-only lane, same strip rule.\n fulfilment: (apiResponse as Record<string, unknown>).fulfilment as FulfilmentInfo | undefined,\n };\n\n // Enforce AstraSync recommendation\n if (result.recommendation === 'deny') {\n // Round-18 G4: recommendation-driven deny lands on the success-path\n // construction (identity was resolved). Flip policy only — identity stays\n // verified — so adapters return 403 (re-auth won't help; the policy\n // decision is the blocker).\n result.policyAllowed = false;\n // 3.6.0: server-sent top-level denialReasons wins; recommendationReasons\n // reconstruction is the fallback for older backends.\n result.denialReasons = serverDenialReasons ??\n result.recommendationReasons ?? ['Access denied by AstraSync recommendation'];\n result.guidance = result.runtimeChallenge\n ? {\n message: `Verification failed: ${result.runtimeChallenge.reason || 'runtime challenge failed'}`,\n registrationUrl: urls?.registrationUrl ?? '',\n documentationUrl: urls?.documentationUrl ?? '',\n }\n : {\n message: result.recommendationReasons?.[0] || 'Access denied by AstraSync recommendation',\n registrationUrl: urls?.registrationUrl ?? '',\n documentationUrl: urls?.documentationUrl ?? '',\n };\n } else if (result.recommendation === 'step_up_required') {\n // The escalation signal rides on `requiresStepUp` / `stepUpApproval`, not\n // on any masquerade of the (now-removed) access band.\n result.requiresStepUp = true;\n result.denialReasons = serverDenialReasons ??\n result.recommendationReasons ?? ['Step-up verification required'];\n }\n\n // Cache the result. Never cache denials (agent may fix challenge endpoint\n // and retry) NOR step-up escalations (3.6.0 — the same bug the Java SDK\n // had: a cached step_up_required verdict keeps denying retries the owner\n // has already approved, and its pollUrl goes stale once consumed/expired).\n // Escalations stay uncached even when the caller pins an explicit cacheTtl.\n const escalates =\n result.requiresStepUp === true ||\n result.requiresApproval === true ||\n result.recommendation === 'step_up_required';\n if (mergedConfig.cacheTtl !== 0 && result.recommendation !== 'deny' && !escalates) {\n cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);\n }\n\n return result;\n}\n\n/**\n * Record a counterparty's grant/deny decision for a verification session.\n * Fire-and-forget — errors are silently swallowed.\n */\nexport async function recordDecision(\n config: GatewayConfig,\n sessionId: string,\n decision: 'granted' | 'denied',\n reason?: string\n): Promise<void> {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n headers['X-API-Key'] = config.apiKey;\n }\n\n await sdkFetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ decision, reason }),\n }).catch(() => {\n /* fire-and-forget */\n });\n}\n\n/**\n * Report post-hoc consideration/outcome data for an attempt chain (Trust\n * Record). Use when the signal never reached verify-access: a\n * catalog served from a local cache, a transport-side deny (sku not found,\n * over budget), or a terminal outcome (settled / blocked on step-up).\n * Authenticated with the same credentials as verify(); the backend responds\n * 202 and folds the data into the attempt chain keyed by `attemptId`.\n *\n * Fire-and-forget — errors are silently swallowed, never throws.\n */\nexport async function reportAttempt(config: GatewayConfig, report: AttemptReport): Promise<void> {\n const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl!;\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n headers['X-API-Key'] = config.apiKey;\n }\n\n // counterpartyId resolution mirrors reportUnregisteredAttempt: an explicit\n // report value wins; otherwise attribute to the configured endpoint.\n const body = {\n ...report,\n ...(!report.counterpartyId &&\n config.counterpartyId && { counterpartyId: config.counterpartyId }),\n };\n\n await sdkFetch(`${apiBaseUrl}/agents/verify-access/attempt-report`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n }).catch(() => {\n /* fire-and-forget */\n });\n}\n\n/**\n * Fetch the per-route policy for an endpoint from the AstraSync backend.\n * v2.9.7 moved policy authority into the dashboard — the SDK no longer\n * accepts `routes` from merchant-side source code, it fetches them from\n * here on init (and refreshes periodically).\n *\n * Returns `null` when the request fails for any reason — the caller decides\n * how to fall back (the middleware allows-all when no policy is loaded so\n * a misconfigured init doesn't take down the merchant's API).\n */\nexport async function fetchRoutes(\n config: GatewayConfig,\n counterpartyId: string\n): Promise<RouteAccessConfigShape[] | null> {\n if (!counterpartyId) return null;\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n headers['X-API-Key'] = config.apiKey;\n }\n try {\n const response = await sdkFetch(\n `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes`,\n { method: 'GET', headers }\n );\n if (!response.ok) return null;\n const body = (await response.json()) as { data?: { routes?: RouteAccessConfigShape[] } };\n return body.data?.routes ?? [];\n } catch {\n return null;\n }\n}\n\n/**\n * Minimal shape of an EndpointRoute as the SDK consumes it. Mirrors the\n * server's `EndpointRoute` type and the SDK's `RouteAccessConfig` — same\n * JSON moves between server and SDK unchanged.\n */\nexport interface RouteAccessConfigShape {\n pattern: string;\n method: string;\n /** Pass through unenforced (observe). Replaces `minAccessLevel: 'none'`. */\n observe?: boolean;\n minTrustScore?: number;\n requiredPurposes?: string[];\n allowedPurposes?: string[];\n allowedJurisdictions?: string[];\n maxDuration?: number;\n maxTransactionValue?: number;\n requirePurpose?: boolean;\n /** Send-mapping — see RouteAccessConfig in types.ts. */\n purpose?: string;\n action?: string;\n}\n\n/**\n * Verify an agent AND automatically record the grant/deny decision.\n *\n * This is the recommended entry point for counterparties that call verify()\n * directly (e.g. MCP servers) rather than using createMiddleware().\n * It adds createSession: true, then fire-and-forgets the decision.\n */\nexport async function verifyAndRecord(\n config: GatewayConfig,\n request: VerificationRequest,\n options?: VerifyOptions\n): Promise<VerificationResult> {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n const result = await verify(mergedConfig, { ...request, createSession: true }, options);\n const sessionId = (result as EnhancedVerificationResult).sessionId;\n\n if (sessionId) {\n // Round-18 G4: a session is \"granted\" only if identity verified AND\n // policy allowed; either failing is a deny.\n if (result.identityVerified && result.policyAllowed) {\n recordDecision(mergedConfig, sessionId, 'granted').catch(() => {});\n } else {\n recordDecision(mergedConfig, sessionId, 'denied', result.denialReasons?.[0]).catch(() => {});\n }\n }\n\n return result;\n}\n\n/**\n * Report an unregistered agent attempt (no AstraSync credentials).\n * Called by SDK adapters when an agent is redirected to /docs/agent-access.\n * Fire-and-forget — errors are silently swallowed.\n */\nexport async function reportUnregisteredAttempt(\n config: GatewayConfig,\n data: {\n /**\n * 3.6.0: optional — the backend attributes by counterpartyId first (sent\n * from config below) and requires at least ONE of counterpartyId/URL.\n * A null/undefined URL is omitted from the body, not sent as null.\n */\n counterpartyUrl?: string;\n counterpartyType?: string;\n sourceIp?: string;\n userAgent?: string;\n requestPath?: string;\n requestMethod?: string;\n /**\n * 3.9.0 — edge-adapter evidence fields, persisted into the\n * event's eventData by the backend: `protocol` = detected transport/\n * credential protocol; `evidence` = light crypto-free detection detail.\n */\n protocol?: string;\n evidence?: Record<string, unknown>;\n /**\n * Maximal metadata capture — the full sanitised metadata for an\n * unregistered caller (every signal legitimately visible on the wire).\n * Persisted into eventData; feeds platform attribution and (via\n * correlationId) registration auto-fill. Fire-and-forget, so free.\n */\n observedMetadata?: ObservedMetadata;\n }\n): Promise<void> {\n const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl!;\n\n const { counterpartyUrl, ...rest } = data;\n const body = {\n ...rest,\n ...(counterpartyUrl && { counterpartyUrl }),\n ...(config.counterpartyId && { counterpartyId: config.counterpartyId }),\n };\n\n await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n }).catch(() => {\n /* fire-and-forget */\n });\n}\n\n/**\n * Report a counterparty-side PDLSS pre-check failure.\n * Called by SDK adapters when the agent's requested PDLSS exceeds\n * counterparty-defined maximums BEFORE calling verify-access.\n * Fire-and-forget — errors are silently swallowed.\n */\nexport async function reportCounterpartyPreCheckFailure(\n config: GatewayConfig,\n data: {\n agentId: string;\n counterpartyUrl: string;\n counterpartyType?: string;\n failures: Array<{\n field: string;\n requested: string | number;\n limit: string | number | string[];\n message: string;\n }>;\n requestPath?: string;\n requestMethod?: string;\n }\n): Promise<void> {\n const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl!;\n\n await sdkFetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(data),\n }).catch(() => {\n /* fire-and-forget */\n });\n}\n\n/**\n * Quick verification — checks credentials and policy in one call.\n *\n * The return shape mirrors `VerificationResult`'s split — partners\n * writing custom handlers around `quickVerify` get the same identity/policy\n * distinction as those calling `verify()` directly. Map to HTTP status the\n * same way: `!identityVerified` → 401; `identityVerified && !policyAllowed`\n * → 403.\n */\nexport async function quickVerify(\n config: GatewayConfig,\n credentials: AgentCredentials\n): Promise<{\n identityVerified: boolean;\n policyAllowed: boolean;\n reason?: string;\n}> {\n const result = await verify(config, {\n credentials,\n purpose: 'verification',\n });\n\n return {\n identityVerified: result.identityVerified,\n policyAllowed: result.policyAllowed,\n reason: result.denialReasons?.[0],\n };\n}\n","/**\n * Maximal metadata capture — the shared, edge-safe sanitiser.\n *\n * AstraSync's thesis is total metadata capture: every header and connection\n * signal an inbound agent presents is a signal we want to keep. This module is\n * the ONE place that decides what is safe to store verbatim, what is a secret\n * to drop, and what credential headers reduce to a safe *format prefix* (a\n * platform signal, never the secret itself).\n *\n * It runs in three places that must agree byte-for-byte:\n * - the edge adapter (`@astrasyncai/adapter-lambda`) — the richest capture\n * point (full CloudFront header arrays),\n * - the SDK adapters (express / nextjs) — for SDK-only merchants with no edge,\n * - (indirectly) the backend, which stores whatever the above forward.\n *\n * Pure — no Node built-ins, no I/O — so it is safe in Lambda@Edge, the browser\n * build, and Deno. Capture is cheap: at the edge and SDK it is local string\n * work, and the backend stores it on a fire-and-forget event insert, so it adds\n * ZERO verify-access decision latency. Detection (which few signals imply which\n * vendor) is a separate, curated concern — see `platform-signatures.ts`.\n */\n\n/**\n * Version of the capture semantics (what is kept, dropped, reduced, or\n * derived — and under which key names).\n *\n * Capture semantics are FROZEN within a package major version: a given\n * `schemaVersion` always means the same field vocabulary and the same\n * keep/drop/reduce rules. The number bumps only on a SEMANTIC change to\n * capture (a renamed key, a changed reduction rule) — never for additive\n * signals, and never merely because the package version moved. To tell\n * builds apart, use the `sdkVersion` already present on the verify body;\n * `schemaVersion` answers the different question \"how do I interpret this\n * stored metadata?\".\n */\nexport const CAPTURE_SCHEMA_VERSION = 1;\n\n/**\n * The captured, sanitised view of an inbound agent's request metadata. Attached\n * to verify-access / beacon payloads as `callerMetadata.observedMetadata`.\n */\nexport interface ObservedMetadata {\n /** Header name → value, secrets removed, verbatim otherwise. */\n headers: Record<string, string>;\n /**\n * Safe key-format prefix of a credential header (e.g. `sk-ant-api03`,\n * `sk-proj`, `AIza`) — a platform signal, NEVER the secret. Absent if no\n * credential header was present or none matched a known format.\n */\n apiKeyFormat?: string;\n /**\n * The subset of `headers` that are known platform-signal headers\n * (`openai-organization`, `x-goog-user-project`, `anthropic-version`, …),\n * pulled out for convenient detection + display. Values are the same\n * sanitised strings as in `headers`.\n */\n platformHeaders?: Record<string, string>;\n /**\n * Connection-layer signals (IP, ASN, country, TLS version, HTTP version,\n * device class, TLS fingerprint). Edge adapters populate this from the\n * platform's native connection surface — the authoritative source. SDK\n * adapters populate it as a fallback via {@link deriveConnectionFromHeaders}\n * when a CDN in front of the merchant injected the equivalent headers;\n * absent when neither source had anything.\n */\n connection?: Record<string, string>;\n /**\n * Number of cookie pairs the caller sent. The `cookie` header VALUE is a\n * secret and is always dropped; the COUNT is a client-shape signal\n * (browsers carry cookies, most automation carries none) that would\n * otherwise be unobservable downstream. Absent when no cookie header\n * arrived.\n */\n cookieCount?: number;\n /**\n * Query-string parameter NAMES (deduped, lowercased) — values are NEVER\n * captured (they routinely carry OAuth codes, tokens, and PII, and no\n * deny-list is complete). Names alone fingerprint client shape. Populated\n * by the edge path, which sees the raw query string.\n */\n queryKeys?: string[];\n /**\n * Version of the capture semantics this object was produced under — see\n * {@link CAPTURE_SCHEMA_VERSION}. Optional for backward compatibility with\n * metadata captured before the field existed.\n */\n schemaVersion?: number;\n}\n\n/**\n * Result of sanitising a raw header map. `headers` is always present (possibly\n * empty); `apiKeyFormat` / `platformHeaders` only when a signal was found.\n */\nexport interface SanitizeHeadersResult {\n headers: Record<string, string>;\n apiKeyFormat?: string;\n platformHeaders?: Record<string, string>;\n /** Cookie-pair count; the cookie value itself is always dropped. */\n cookieCount?: number;\n}\n\n/**\n * Header names dropped entirely — genuine secrets or our own credentials that\n * describe the request's transport, not the agent. Matched case-insensitively\n * on the exact header name.\n */\nconst SECRET_HEADER_NAMES = new Set([\n 'cookie',\n 'set-cookie',\n 'x-csrf-token',\n 'proxy-authorization',\n 'x-astrasync-signature',\n 'x-astrasync-secret',\n 'x-hub-signature',\n 'x-hub-signature-256',\n // Short-lived verify dedupe token — must never persist in stored metadata.\n 'x-astra-verified-hop',\n]);\n\n/**\n * Header-name prefixes dropped entirely — our own credential / signing surface\n * (`x-astrasync-*`, `kya-*`) must never round-trip into stored metadata.\n */\nconst SECRET_HEADER_PREFIXES = ['x-astrasync-', 'kya-', 'kya_'];\n\n/**\n * Credential headers whose VALUE is a secret but whose FORMAT is a signal. We\n * keep the leading format prefix (up to the random tail) and drop the rest.\n */\nconst CREDENTIAL_HEADER_NAMES = new Set([\n 'authorization',\n 'x-api-key',\n 'x-goog-api-key',\n 'api-key',\n]);\n\n/**\n * Header names that carry a raw signature value (drop the value; the presence\n * is not worth a bloat risk). Matched as a suffix so `x-*-signature` variants\n * are covered without an exhaustive list.\n */\nfunction isSignatureHeader(name: string): boolean {\n return name === 'signature' || name.endsWith('-signature') || name.endsWith('-signature-256');\n}\n\n/**\n * Known platform-signal headers — pulled into `platformHeaders` for convenient\n * detection + display. Exact names plus a few prefixes (`x-goog-`,\n * `x-stainless-` — the OpenAI/Anthropic SDK client fingerprint headers).\n */\nconst PLATFORM_HEADER_NAMES = new Set([\n 'openai-organization',\n 'openai-project',\n 'openai-version',\n 'openai-processing-ms',\n 'anthropic-version',\n 'anthropic-beta',\n 'x-goog-user-project',\n 'x-goog-api-client',\n 'user-agent',\n 'x-agent-card-url',\n 'x-agent-provider',\n]);\n// `sec-ch-*` client hints are platform signals: Chromium forks (agentic\n// browsers included) often carry their real brand in `sec-ch-ua` even when\n// the UA string mimics stock Chrome.\nconst PLATFORM_HEADER_PREFIXES = [\n 'x-goog-',\n 'x-stainless-',\n 'x-anthropic-',\n 'x-openai-',\n 'sec-ch-',\n];\n\n/**\n * Known API-key format prefixes, longest-first so `sk-ant-api03` wins over\n * `sk-ant` and `sk-`. Each is the SAFE, non-secret leading token of a\n * credential; the random tail is never captured.\n */\nconst KEY_FORMAT_PREFIXES = [\n 'sk-ant-api03',\n 'sk-ant-api',\n 'sk-ant',\n 'sk-proj',\n 'sk-svcacct',\n 'sk-or-v1',\n 'sk-or',\n 'sk-lf',\n 'sk-',\n 'gsk_',\n 'aiza',\n 'ya29',\n 'ghp_',\n 'gho_',\n 'github_pat_',\n 'xai-',\n 'pplx-',\n 'r8_',\n 'hf_',\n 'astra-',\n 'astrae-',\n];\n\n/** Size caps — the only guard against jsonb bloat from a hostile POST. */\nexport const MAX_HEADERS = 64;\nexport const MAX_HEADER_VALUE_BYTES = 1024;\nexport const MAX_TOTAL_BYTES = 16 * 1024;\n\nfunction byteLength(s: string): number {\n // Edge-safe (no Buffer): count UTF-8 bytes without allocating a Buffer.\n let bytes = 0;\n for (let i = 0; i < s.length; i++) {\n const code = s.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff) {\n bytes += 4;\n i++; // surrogate pair\n } else bytes += 3;\n }\n return bytes;\n}\n\n/**\n * Extract the safe key-format prefix from a credential header value. Strips a\n * leading `Bearer ` / `Basic ` scheme, then returns the longest known prefix\n * that the (lowercased) value starts with. Returns undefined for opaque values\n * (e.g. a bare UUID token) so we never guess a format we don't recognise.\n */\nexport function extractApiKeyFormat(rawValue: string): string | undefined {\n if (!rawValue) return undefined;\n let value = rawValue.trim();\n const spaceIdx = value.indexOf(' ');\n if (spaceIdx > 0) {\n const scheme = value.slice(0, spaceIdx).toLowerCase();\n if (scheme === 'bearer' || scheme === 'basic' || scheme === 'token') {\n value = value.slice(spaceIdx + 1).trim();\n }\n }\n const lower = value.toLowerCase();\n for (const prefix of KEY_FORMAT_PREFIXES) {\n if (lower.startsWith(prefix)) {\n // Return the prefix as-declared in the list (canonical casing).\n return prefix;\n }\n }\n return undefined;\n}\n\nfunction isSecretHeader(name: string): boolean {\n if (SECRET_HEADER_NAMES.has(name)) return true;\n if (SECRET_HEADER_PREFIXES.some((p) => name.startsWith(p))) return true;\n if (isSignatureHeader(name)) return true;\n return false;\n}\n\nfunction isPlatformHeader(name: string): boolean {\n if (PLATFORM_HEADER_NAMES.has(name)) return true;\n return PLATFORM_HEADER_PREFIXES.some((p) => name.startsWith(p));\n}\n\n/**\n * Sanitise a raw header map for storage. Deny-lists genuine secrets, reduces\n * credential headers to a safe format prefix, keeps everything else verbatim,\n * and enforces the size caps that bound jsonb growth.\n *\n * Accepts any string→(string|string[]|undefined) map (Node's `req.headers`,\n * CloudFront's flattened headers, a plain object). Multi-value headers are\n * joined with `, ` per RFC 7230.\n */\nexport function sanitizeHeaders(\n raw: Record<string, string | string[] | undefined> | undefined | null\n): SanitizeHeadersResult {\n const headers: Record<string, string> = {};\n const platformHeaders: Record<string, string> = {};\n let apiKeyFormat: string | undefined;\n let cookieCount: number | undefined;\n let totalBytes = 0;\n let count = 0;\n\n if (!raw) return { headers };\n\n for (const rawName of Object.keys(raw)) {\n if (count >= MAX_HEADERS) break;\n const name = rawName.toLowerCase();\n const rawValue = raw[rawName];\n if (rawValue === undefined) continue;\n const joined = Array.isArray(rawValue) ? rawValue.join(', ') : String(rawValue);\n\n // Credential headers: capture the format prefix, drop the secret.\n if (CREDENTIAL_HEADER_NAMES.has(name)) {\n const fmt = extractApiKeyFormat(joined);\n if (fmt && !apiKeyFormat) apiKeyFormat = fmt;\n continue;\n }\n\n // Cookie: the value is a secret (dropped below), but the PAIR COUNT is a\n // client-shape signal worth keeping.\n if (name === 'cookie') {\n cookieCount = joined.split(';').filter((s) => s.trim().length > 0).length;\n }\n\n // Genuine secrets: drop entirely.\n if (isSecretHeader(name)) continue;\n\n // Everything else describing the agent: keep, bounded.\n let value = joined;\n if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {\n value = value.slice(0, MAX_HEADER_VALUE_BYTES);\n }\n const entryBytes = byteLength(name) + byteLength(value);\n if (totalBytes + entryBytes > MAX_TOTAL_BYTES) continue;\n totalBytes += entryBytes;\n count++;\n headers[name] = value;\n if (isPlatformHeader(name)) platformHeaders[name] = value;\n }\n\n const result: SanitizeHeadersResult = { headers };\n if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;\n if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;\n if (cookieCount !== undefined) result.cookieCount = cookieCount;\n return result;\n}\n\n/**\n * Pull the known platform-signal headers out of an ALREADY-sanitised header\n * map. `sanitizeHeaders` already computes this as a by-product; this standalone\n * form is exported for call-sites (edge, tests) that hold a sanitised map and\n * want only the platform subset. Never re-run over raw (unsanitised) headers.\n */\nexport function extractPlatformHeaders(\n sanitized: Record<string, string> | undefined | null\n): Record<string, string> {\n const out: Record<string, string> = {};\n if (!sanitized) return out;\n for (const name of Object.keys(sanitized)) {\n if (isPlatformHeader(name.toLowerCase())) out[name.toLowerCase()] = sanitized[name];\n }\n return out;\n}\n\n/**\n * Precedence-ordered CDN header sources for each derived `connection` key.\n * First present header wins; the special `x-forwarded-for` source takes the\n * LEFTMOST (client) entry of the comma-separated chain.\n */\nconst CONNECTION_HEADER_SOURCES: ReadonlyArray<readonly [key: string, headers: readonly string[]]> =\n [\n ['ip', ['cf-connecting-ip', 'fastly-client-ip', 'true-client-ip', 'x-real-ip']],\n [\n 'country',\n ['cf-ipcountry', 'x-vercel-ip-country', 'cloudfront-viewer-country', 'fastly-geo-country'],\n ],\n ['region', ['x-vercel-ip-country-region', 'cloudfront-viewer-country-region']],\n ['countryRegionName', ['cloudfront-viewer-country-region-name']],\n ['city', ['x-vercel-ip-city', 'cloudfront-viewer-city']],\n ['latitude', ['x-vercel-ip-latitude', 'cloudfront-viewer-latitude']],\n ['longitude', ['x-vercel-ip-longitude', 'cloudfront-viewer-longitude']],\n ['postalCode', ['x-vercel-ip-postal-code', 'cloudfront-viewer-postal-code']],\n ['asn', ['cloudfront-viewer-asn']],\n ['tlsVersion', ['cloudfront-viewer-tls']],\n ['httpVersion', ['cloudfront-viewer-http-version']],\n ['timeZone', ['x-vercel-ip-timezone', 'cloudfront-viewer-time-zone']],\n ['metroCode', ['cloudfront-viewer-metro-code']],\n // TLS fingerprints can only be computed where TLS terminates. When a CDN\n // terminates TLS in front of the merchant, its fingerprint headers ARE the\n // connection-layer truth — normalising them here is the permanent design.\n // CloudFront delivers JA3/JA4 only via an origin request policy and only\n // for HTTPS viewer connections.\n ['tlsFingerprint', ['cf-ja3-hash', 'cloudfront-viewer-ja3-fingerprint']],\n ['ja4', ['cf-ja4', 'cloudfront-viewer-ja4-fingerprint']],\n // Header-structure fingerprint: browser/SDK-distinctive header ordering.\n ['headerOrder', ['cloudfront-viewer-header-order']],\n ['headerCount', ['cloudfront-viewer-header-count']],\n ];\n\n/**\n * Backend bound: observedMetadata.connection values longer than this fail\n * the platform's zod parse outright (boundedStringRecord(32, 256)), so every\n * emitter truncates — headerOrder routinely exceeds it.\n */\nexport const MAX_CONNECTION_VALUE_CHARS = 256;\n\n/**\n * Derive the connection-layer block from CDN-injected request headers.\n *\n * When a merchant's SDK-gated origin sits behind a CDN (Cloudflare, Fastly,\n * Vercel, CloudFront), the CDN has already seen the connection layer and\n * injected it as headers. This promotes those headers to the same\n * `connection` keys the edge adapters populate natively, so SDK-only\n * deployments still get IP / geo / TLS attribution. Precedence per key:\n *\n * - `ip`: `cf-connecting-ip` > `fastly-client-ip` > `true-client-ip` >\n * `x-real-ip` > `cloudfront-viewer-address` (port stripped) > leftmost\n * `x-forwarded-for`\n * - `country`: `cf-ipcountry` > `x-vercel-ip-country` >\n * `cloudfront-viewer-country` > `fastly-geo-country`\n * - `region` / `city` / `timeZone`: Vercel then CloudFront viewer headers\n * - `countryRegionName` / `asn` / `tlsVersion` / `httpVersion` /\n * `metroCode` / `headerOrder` / `headerCount`: `cloudfront-viewer-*`\n * - `tlsFingerprint`: `cf-ja3-hash` > `cloudfront-viewer-ja3-fingerprint`\n * - `ja4`: `cf-ja4` > `cloudfront-viewer-ja4-fingerprint`\n *\n * All keys are additive under `schemaVersion` 1 (the frozen-capture doctrine\n * bumps only for changed semantics, never for additive signals). Values are\n * truncated to MAX_CONNECTION_VALUE_CHARS — the platform's hard bound.\n *\n * Edge adapters keep their richer native collectors — native platform\n * signals are authoritative; this header derivation is the SDK-level\n * fallback. Header names are matched case-insensitively. Returns undefined\n * when no source header is present, so the `connection` key is simply\n * omitted (matching edge behaviour).\n */\nexport function deriveConnectionFromHeaders(\n headers: Record<string, string> | undefined | null\n): Record<string, string> | undefined {\n if (!headers) return undefined;\n const lower: Record<string, string> = {};\n for (const name of Object.keys(headers)) {\n const value = headers[name];\n if (typeof value === 'string' && value.length > 0) lower[name.toLowerCase()] = value;\n }\n\n const connection: Record<string, string> = {};\n for (const [key, sources] of CONNECTION_HEADER_SOURCES) {\n for (const source of sources) {\n const value = lower[source]?.trim();\n if (value) {\n connection[key] = value.slice(0, MAX_CONNECTION_VALUE_CHARS);\n break;\n }\n }\n }\n // ip fallback 1: cloudfront-viewer-address arrives as ip:port — strip the\n // trailing port only (IPv6-safe), unlike the dedicated client-ip headers.\n // The port itself is kept as its own key: ephemeral-port patterns signal\n // NAT/proxy connection reuse.\n {\n const addr = lower['cloudfront-viewer-address']?.trim();\n const portMatch = addr?.match(/:(\\d+)$/);\n if (portMatch) connection.sourcePort = portMatch[1];\n if (!connection.ip && addr) connection.ip = addr.replace(/:\\d+$/, '');\n }\n // ip fallback 2: leftmost (client) hop of the x-forwarded-for chain.\n if (!connection.ip) {\n const xff = lower['x-forwarded-for'];\n const client = xff?.split(',')[0]?.trim();\n if (client) connection.ip = client;\n }\n // tlsCipher: CloudFront folds the cipher into the viewer-tls string\n // (`TLSv1.3:TLS_AES_128_GCM_SHA256:fullHandshake`). `tlsVersion` stays\n // verbatim (reducing it would be a semantic change → schema v2); the\n // cipher is pulled out additively so it is queryable on its own.\n if (!connection.tlsCipher) {\n const cipher = connection.tlsVersion?.split(':')[1];\n if (cipher) connection.tlsCipher = cipher;\n }\n\n return Object.keys(connection).length > 0 ? connection : undefined;\n}\n\n/**\n * The FULL metadata capture for one request at an SDK adapter (express /\n * nextjs / mcp): sanitised headers, the safe credential format prefix, the\n * platform-signal subset, the CDN-derived connection block, and the capture\n * schema version. This is the ONE builder all SDK adapters share, so their\n * verify-access bodies agree byte-for-byte.\n *\n * Connection derivation runs over the RAW header map (pre-cap) — the size\n * caps guard storage growth and must never hide a geo/IP header that\n * happened to arrive late in an oversized map.\n */\nexport function buildSdkObservedMetadata(\n rawHeaders: Record<string, string | string[] | undefined> | undefined | null\n): ObservedMetadata {\n const { headers, apiKeyFormat, platformHeaders, cookieCount } = sanitizeHeaders(rawHeaders);\n const flat: Record<string, string> = {};\n if (rawHeaders) {\n for (const name of Object.keys(rawHeaders)) {\n const value = rawHeaders[name];\n if (value === undefined) continue;\n flat[name.toLowerCase()] = Array.isArray(value) ? value.join(', ') : String(value);\n }\n }\n const connection = deriveConnectionFromHeaders(flat);\n return {\n headers,\n ...(apiKeyFormat && { apiKeyFormat }),\n ...(platformHeaders && { platformHeaders }),\n ...(connection && { connection }),\n ...(cookieCount !== undefined && { cookieCount }),\n schemaVersion: CAPTURE_SCHEMA_VERSION,\n };\n}\n","/**\n * Direct-path step-up approval gate (post-3.1.0 F2 follow-up).\n *\n * The verify-access spending-Limit ladder returns an \"allowed-but-escalate\"\n * outcome for a transaction value in the human-in-the-loop band\n * `[autonomousThreshold, hardLimit)`: `policyAllowed: true` (it is NOT a deny)\n * plus `requiresStepUp` / `requiresApproval`. The bridge blocks these; the\n * direct-path adapters historically gated on `identityVerified && policyAllowed`\n * alone and would therefore ADMIT (and let the merchant settle) the very\n * purchase the bridge blocks - a cross-path bypass.\n *\n * The backend now creates a pending step-up approval record and returns a\n * `stepUpApproval` object with `{ approvalId, pollUrl, expiresAt }` in the\n * verify-access response. The owner can approve/deny from the dashboard\n * (Activity → Pending Verifications). Future channels: mobile push, browser\n * extension.\n *\n * Direct-path adapters (Express/MCP/Next.js) fail closed BY DEFAULT. The\n * `stepUpApproval` info is included in the deny response so integrations can\n * surface it (\"your agent owner needs to approve this\") or poll it themselves.\n * 3.6.0 adds an opt-in hold-and-poll (`stepUpMaxWaitMs` on GatewayConfig, see\n * ./stepup-poll.ts): the adapter holds the request, polls the approval, and on\n * 'approved' re-verifies once (cache-bypassed) so the backend redeems the\n * single-use approval — the MFA model the Java SDK ships default-on. Opt-in\n * here because a minor release must not silently convert instant 403s into\n * multi-minute holds (client timeouts, connection pools, edge wall-clocks).\n *\n * Detection keys on the granular Limit flags (`requiresStepUp` /\n * `requiresApproval`, set from the verify-access `access` block) rather than\n * the `recommendation` umbrella - both because those flags ARE what drive a\n * `step_up_required` recommendation server-side, and because it keeps the\n * unverified-agent-policy `audit` outcome (a deliberate allow-and-log) from\n * being caught here.\n */\nimport type { VerificationResult, StepUpApprovalInfo } from '../types';\n\nconst APPROVAL_REASON =\n 'Transaction is above the autonomous limit and requires human approval. The agent owner can approve or deny this transaction in the AstraSync dashboard.';\n\n/** True when the outcome needs human step-up/approval. */\nexport function requiresHumanApproval(result: VerificationResult): boolean {\n return result.requiresStepUp === true || result.requiresApproval === true;\n}\n\n/**\n * Annotate a result in place with the structured approval-required failure +\n * denial reason, so the adapters' existing deny renderers surface it like any\n * other failure. Mirrors the bridge's `commerce.intent.approval_required`.\n */\nexport function annotateApprovalRequired(result: VerificationResult): void {\n result.failures = [\n ...(result.failures ?? []),\n { dimension: 'commerce.intent.approval_required', message: APPROVAL_REASON },\n ];\n result.denialReasons = [APPROVAL_REASON, ...(result.denialReasons ?? [])];\n}\n\n/**\n * Extract step-up approval polling info from the verification result, if\n * present. Returns undefined when no approval was created (e.g. transaction\n * below the autonomous threshold, or the backend couldn't create one).\n */\nexport function getApprovalPollingInfo(result: VerificationResult): StepUpApprovalInfo | undefined {\n return result.stepUpApproval;\n}\n","/**\n * Step-up hold-and-poll (3.6.0) — the synchronous half of the MFA-style gate,\n * mirroring the Java SDK's `awaitStepUpApproval` / `pollStepUpStatus`.\n *\n * A verify() that comes back step_up_required carries a `stepUpApproval`\n * handle: `{ approvalId, pollUrl, expiresAt }`. The adapter HOLDS the original\n * request, polls the approval status, and on 'approved' re-verifies the same\n * request once (cache-bypassed) so the backend redeems the single-use approval\n * and returns a grant. Any other terminal outcome falls into the adapter's\n * normal fail-closed deny path.\n *\n * The poll endpoint is deliberately unauthenticated (it sits above the\n * backend router's `authenticate` middleware): the unguessable UUIDv4\n * approvalId is the capability token, and the route is rate-limited\n * (60/min/IP). No Authorization header is sent.\n */\n\nimport { sdkFetch } from '../http';\nimport { GatewayConfig, StepUpApprovalInfo, StepUpApprovalStatus, StepUpOutcome } from '../types';\n\nconst DEFAULT_POLL_INTERVAL_MS = 3000;\nconst MIN_POLL_INTERVAL_MS = 250;\nconst FALLBACK_MAX_WAIT_MS = 5 * 60 * 1000;\nconst EXPIRY_GRACE_MS = 5000;\nconst POLL_REQUEST_TIMEOUT_MS = 10_000;\n\n/** Resolve the absolute poll URL. pollUrl is server-relative\n * (`/api/step-up-approvals/poll/:id`), so join it to apiBaseUrl minus its\n * `/api` suffix (the origin) — same idiom as well-known.ts. Absolute URLs\n * pass through untouched. */\nfunction resolvePollUrl(config: GatewayConfig, pollUrl: string): string {\n if (pollUrl.startsWith('http')) return pollUrl;\n const origin = (config.apiBaseUrl ?? '').replace(/\\/api\\/?$/, '');\n return origin + pollUrl;\n}\n\n/**\n * One GET of the approval's poll endpoint. Returns the status string, or\n * null on any error / non-2xx / unparsable body — the hold loop treats null\n * as transient and just polls again.\n */\nexport async function pollStepUpStatus(\n config: GatewayConfig,\n approval: StepUpApprovalInfo\n): Promise<StepUpApprovalStatus | null> {\n if (!approval.pollUrl) return null;\n try {\n const res = await sdkFetch(resolvePollUrl(config, approval.pollUrl), {\n method: 'GET',\n signal: AbortSignal.timeout(POLL_REQUEST_TIMEOUT_MS),\n });\n if (!res.ok) return null;\n const body = (await res.json()) as Record<string, unknown>;\n // Envelope shape: { success, data: { status, resolvedAt } } — a flat\n // `status` read hangs every hold (live-UAT catch). Envelope-first with a\n // flat fallback for forward compatibility, exactly like the Java SDK.\n const data = body?.data;\n const status =\n data && typeof data === 'object' ? (data as Record<string, unknown>).status : body?.status;\n return typeof status === 'string' ? (status as StepUpApprovalStatus) : null;\n } catch {\n return null; // transient poll failure — the loop just polls again\n }\n}\n\nfunction resolveDeadline(config: GatewayConfig, approval: StepUpApprovalInfo): number {\n const now = Date.now();\n const maxWait = config.stepUpMaxWaitMs ?? 0;\n if (maxWait > 0) return now + maxWait;\n // stepUpMaxWaitMs === 0: hold until the approval itself expires (+ a small\n // grace so an approval landing at the buzzer still resolves on the next\n // poll). Missing/unparsable expiresAt → fixed 5-min fallback (Java parity).\n if (approval.expiresAt) {\n const expiry = Date.parse(approval.expiresAt);\n if (!Number.isNaN(expiry)) return Math.max(now, expiry) + EXPIRY_GRACE_MS;\n }\n return now + FALLBACK_MAX_WAIT_MS;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Block until a pending step-up approval reaches a terminal state or the wait\n * budget runs out. Returns the terminal status, or 'timeout'.\n *\n * Unknown non-pending statuses (a future backend state) still terminate the\n * hold — they are returned as-is and land in the deny path, which is the safe\n * reading of \"not approved\".\n */\nexport async function awaitStepUpApproval(\n config: GatewayConfig,\n approval: StepUpApprovalInfo\n): Promise<StepUpOutcome> {\n const deadline = resolveDeadline(config, approval);\n const pollInterval = Math.max(\n MIN_POLL_INTERVAL_MS,\n config.stepUpPollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS\n );\n\n while (Date.now() < deadline) {\n const status = await pollStepUpStatus(config, approval);\n if (status && status !== 'pending') {\n return status as StepUpOutcome;\n }\n const remaining = deadline - Date.now();\n if (remaining <= 0) break;\n await sleep(Math.min(pollInterval, remaining));\n }\n return 'timeout';\n}\n","/**\n * X-Astra-Verified-Hop — cross-hop verify-access dedupe marker.\n *\n * Header carrying upstream verify-access proof so an inner-hop endpoint can\n * dedupe (skip verify-access when the same ASTRA-id was already verified a\n * few ms earlier). Emitted by the MCP adapter toward tool fetches and by the\n * edge gateway toward the origin at authenticate/authorize depth. Value\n * format:\n *\n * {astraId};{sessionId};{checkedAt-ms}\n *\n * Receivers MUST validate that `checkedAt` is recent (≤ 60s window\n * recommended) and that `astraId` matches the agent identity claimed on the\n * inner hop. The header alone is NOT proof-of-identity — it's an UNSIGNED\n * dedupe advisory: trust it (`trustVerifiedHop`) only where every network\n * path to the inner hop crosses a stripping gateway or an equivalent trusted\n * boundary, exactly like the unsigned X-AstraSync-* attestation headers.\n * Pair with the existing X-Astra-Id auth.\n */\n\nexport const MCP_VERIFIED_HOP_HEADER = 'X-Astra-Verified-Hop';\n\n/** Default acceptable age (ms) for an X-Astra-Verified-Hop header. */\nexport const MCP_VERIFIED_HOP_MAX_AGE_MS = 60_000;\n\nexport interface VerifiedHopMarker {\n astraId: string;\n sessionId?: string;\n checkedAt: number; // epoch ms\n}\n\nexport function serializeVerifiedHop(marker: VerifiedHopMarker): string {\n return `${marker.astraId};${marker.sessionId ?? ''};${marker.checkedAt}`;\n}\n\nexport function parseVerifiedHop(value: string | undefined | null): VerifiedHopMarker | null {\n if (!value) return null;\n const parts = value.split(';');\n if (parts.length !== 3) return null;\n const [astraId, sessionId, checkedAtRaw] = parts;\n if (!astraId) return null;\n const checkedAt = Number(checkedAtRaw);\n if (!Number.isFinite(checkedAt) || checkedAt <= 0) return null;\n return {\n astraId,\n ...(sessionId ? { sessionId } : {}),\n checkedAt,\n };\n}\n\n/**\n * Returns true when `marker.astraId` matches the inner-hop's claimed\n * ASTRA-id AND the marker is recent enough. Inner-hop middleware uses this\n * to skip a duplicate verify-access call.\n */\nexport function isVerifiedHopValidFor(\n marker: VerifiedHopMarker | null,\n expectedAstraId: string,\n opts: { maxAgeMs?: number; now?: number } = {}\n): boolean {\n if (!marker) return false;\n if (marker.astraId !== expectedAstraId) return false;\n const maxAge = opts.maxAgeMs ?? MCP_VERIFIED_HOP_MAX_AGE_MS;\n const now = opts.now ?? Date.now();\n return now - marker.checkedAt <= maxAge && now >= marker.checkedAt;\n}\n","/**\n * MCP server-side helpers — companion to `transport/mcp.ts` (which handles the\n * agent-side `_meta.astrasync` block).\n *\n * Surfaces a body-aware policy hook the existing `createMiddleware` couldn't\n * provide — MCP traffic is JSON-RPC over a single endpoint (`/mcp`), so the\n * default route-pattern gating is too coarse: every request looks the same\n * URL-wise, but `initialize` is low-risk handshake while `tools/call` of a\n * payment tool is high-risk. Cohort-3 beta merchants flagged this 🟡 in the\n * v2.9.5 round.\n *\n * What lives here:\n * - `parseMcpJsonRpc(body)` — peels JSON-RPC method + tool name + agent\n * id without committing to a particular MCP\n * server framework.\n * - `mcpToPdlss(parsed)` — canonical mapping JSON-RPC method → PDLSS\n * purpose / action / resource. Doc-stable\n * so audits can correlate.\n * - `mcpDefaultEnforced(parsed)` — whether a method is enforced by default\n * so a single MCP middleware can split\n * `initialize` / `tools/list` (pass-through)\n * from `tools/call` (enforced).\n * - `MCP_VERIFIED_HOP_HEADER` — header convention for the dedupe pattern\n * when an MCP tool calls an inner REST hop.\n * - `serialize/parseVerifiedHop` helpers.\n *\n * The Express MCP adapter is in `adapters/mcp.ts` and consumes these.\n */\n\n// X-Astra-Verified-Hop helpers live in ../verified-hop (shared with the\n// edge gateway's origin-bound emission); re-exported here so existing\n// transport-level imports keep working unchanged.\nexport {\n MCP_VERIFIED_HOP_HEADER,\n MCP_VERIFIED_HOP_MAX_AGE_MS,\n serializeVerifiedHop,\n parseVerifiedHop,\n isVerifiedHopValidFor,\n} from '../verified-hop';\nexport type { VerifiedHopMarker } from '../verified-hop';\n\n/**\n * Output of `parseMcpJsonRpc`. Self-describing so the middleware doesn't have\n * to re-introspect the body to figure out gating.\n */\nexport interface ParsedMcpRequest {\n /** JSON-RPC method (e.g. `tools/call`, `initialize`, `tools/list`). */\n method: string;\n /** Set when method === 'tools/call'; the tool name from `params.name`. */\n toolName?: string;\n /** Initialize-specific protocolVersion handshake info, when present. */\n protocolVersion?: string;\n /**\n * Agent id read from the body, in priority order:\n * 1. `params._meta.astrasync.agentId` (the canonical SDK location, see\n * `transport/mcp.ts → setMcpMeta`)\n * 2. `params.arguments.agent_id` (legacy / hand-written tool callers)\n * Header-supplied id (X-Astra-Id) is read separately by the adapter and\n * compared to this for the mismatch check.\n */\n agentIdFromBody?: string;\n /**\n * Purpose extracted from the MCP body\n * with the symmetric precedence chain. Sourced from `_meta.astrasync.purpose`\n * (canonical SDK location) OR `params.arguments.purpose` (legacy /\n * conventional callers). The discriminator is on `purposeSourceFromBody`.\n * Adapter combines this with the `X-Astra-Purpose` header (header wins)\n * before mapping; final fallback at `mcpToPdlss` is `undefined`.\n */\n purposeFromBody?: string;\n /** Which body location resolved `purposeFromBody`. */\n purposeSourceFromBody?: 'meta' | 'tool_argument';\n /**\n * Action extracted from the MCP body with the same\n * symmetric chain as purpose. Sourced from `_meta.astrasync.action`\n * (canonical) OR `params.arguments.action` (legacy). Adapter combines\n * with the `X-Astra-Action` header (header wins) before mapping; final\n * fallback at `mcpToPdlss` is the transport-layer default\n * (`tools/call:<toolname>` or just `<method>`).\n */\n actionFromBody?: string;\n /** Which body location resolved `actionFromBody`. */\n actionSourceFromBody?: 'meta' | 'tool_argument';\n /** True for handshake methods that must succeed before any tool call. */\n isInitialize: boolean;\n /** True for `tools/call`. */\n isToolCall: boolean;\n /** True for low-risk introspection (`tools/list`, `prompts/list`, etc.). */\n isIntrospection: boolean;\n}\n\n/**\n * Peel the JSON-RPC envelope. Returns `null` if the body isn't a JSON-RPC\n * request (callers can short-circuit with a 400 or treat as untyped traffic).\n *\n * Accepts both single-request and notification shapes. Batch requests are\n * NOT supported here — the verify-access contract is single-agent-per-call;\n * a batch body should be split before policy gating.\n */\nexport function parseMcpJsonRpc(body: unknown): ParsedMcpRequest | null {\n if (!body || typeof body !== 'object' || Array.isArray(body)) return null;\n const obj = body as Record<string, unknown>;\n if (obj.jsonrpc !== '2.0' && obj.jsonrpc !== '1.0') return null;\n const method = typeof obj.method === 'string' ? obj.method : null;\n if (!method) return null;\n const params = obj.params as Record<string, unknown> | undefined;\n\n // Tool-name lookup. MCP `tools/call` puts the tool under `params.name`.\n let toolName: string | undefined;\n if (method === 'tools/call' && params && typeof params.name === 'string') {\n toolName = params.name;\n }\n\n // protocolVersion only on the initialize request.\n let protocolVersion: string | undefined;\n if (method === 'initialize' && params && typeof params.protocolVersion === 'string') {\n protocolVersion = params.protocolVersion;\n }\n\n const meta = params?._meta as Record<string, unknown> | undefined;\n const astrasyncMeta = meta?.astrasync as Record<string, unknown> | undefined;\n const args = params?.arguments as Record<string, unknown> | undefined;\n\n // Agent-id lookup with documented precedence: _meta.astrasync.agentId\n // wins over params.arguments.agent_id. (The argument-shape uses\n // `agent_id` snake_case for legacy conventional callers; meta is the\n // canonical SDK location.)\n let agentIdFromBody: string | undefined;\n if (astrasyncMeta && typeof astrasyncMeta.agentId === 'string') {\n agentIdFromBody = astrasyncMeta.agentId;\n } else if (args && typeof args.agent_id === 'string') {\n agentIdFromBody = args.agent_id;\n }\n\n // Round-13 (R13-1, R13-2) — symmetric body-extraction for purpose +\n // action. Per `feedback_symmetric_fallbacks_for_symmetric_concepts.md`:\n // these two concepts are paired in the MCP middleware, so their\n // fallback chains MUST mirror each other exactly. Drift between them\n // generates the same support-ticket class repeatedly (\"I set X in\n // arguments and it didn't take\" — happened for purpose in round-12\n // post-F19, and was about to happen for action in round-13).\n //\n // Body precedence (applies to both): _meta.astrasync.<key> wins over\n // params.arguments.<key>. The discriminator is exported alongside the\n // value so the adapter can fold it into purposeSource / actionSource\n // for debug logging and adoption tracking.\n const purposeBodyResult = extractFromMcpBody(astrasyncMeta, args, 'purpose');\n const actionBodyResult = extractFromMcpBody(astrasyncMeta, args, 'action');\n\n const isInitialize = method === 'initialize';\n const isToolCall = method === 'tools/call';\n const isIntrospection =\n method === 'tools/list' ||\n method === 'prompts/list' ||\n method === 'resources/list' ||\n method === 'ping' ||\n method === 'notifications/initialized';\n\n return {\n method,\n ...(toolName ? { toolName } : {}),\n ...(protocolVersion ? { protocolVersion } : {}),\n ...(agentIdFromBody ? { agentIdFromBody } : {}),\n ...(purposeBodyResult.value ? { purposeFromBody: purposeBodyResult.value } : {}),\n ...(purposeBodyResult.source ? { purposeSourceFromBody: purposeBodyResult.source } : {}),\n ...(actionBodyResult.value ? { actionFromBody: actionBodyResult.value } : {}),\n ...(actionBodyResult.source ? { actionSourceFromBody: actionBodyResult.source } : {}),\n isInitialize,\n isToolCall,\n isIntrospection,\n };\n}\n\n/**\n * Shared body-extraction helper for the symmetric purpose + action\n * fallback chain. Returns the resolved value AND\n * the source discriminator so adapters can fold it into per-call debug\n * logs (`purpose_source` / `action_source`).\n *\n * Precedence:\n * 1. `params._meta.astrasync.<key>` — canonical SDK location, source: 'meta'\n * 2. `params.arguments.<key>` — legacy / conventional, source: 'tool_argument'\n * 3. neither present — value: undefined, source: undefined\n *\n * The HTTP-header tier (`X-Astra-<concept>`) is the adapter's job; this\n * helper only handles body sources. Adapters combine the two: header\n * wins over body. See `mcpToPdlss` for the final precedence assembly.\n */\nfunction extractFromMcpBody(\n astrasyncMeta: Record<string, unknown> | undefined,\n args: Record<string, unknown> | undefined,\n key: 'purpose' | 'action'\n): { value: string | undefined; source: 'meta' | 'tool_argument' | undefined } {\n if (astrasyncMeta && typeof astrasyncMeta[key] === 'string') {\n return { value: astrasyncMeta[key] as string, source: 'meta' };\n }\n if (args && typeof args[key] === 'string') {\n return { value: args[key] as string, source: 'tool_argument' };\n }\n return { value: undefined, source: undefined };\n}\n\n/**\n * PDLSS mapping for an MCP request. The platform's PDLSS taxonomy is\n * `purpose / action / resource`; for MCP traffic the audit-useful dimensions\n * are the JSON-RPC method and (for `tools/call`) the tool name.\n *\n * v2.5.0 rules:\n * - `resource` defaults to the HTTP request path (e.g. `/mcp`). Per-tool\n * overrides via `toolGates` config let merchants map tools to specific\n * backend resources (e.g. `/api/catalog`).\n * - `purpose` defaults to `undefined` (backend evaluator applies\n * skip-when-undefined). Per-tool overrides via `toolGates` config let\n * merchants declare the semantic purpose each tool fulfils.\n * - `action` comes only from declarations (toolGate config, header, or\n * body). For `tools/call` with none declared it is `undefined` —\n * evaluation is purpose-only, because a raw tool name is a transport\n * token, not a PDLSS action, and sending it as one guarantees an\n * action-policy mismatch. For other JSON-RPC methods the method string\n * is used.\n */\nexport interface McpPdlssMapping {\n purpose: string | undefined;\n /**\n * `undefined` when a `tools/call` request declared no action anywhere —\n * the verify-access call then evaluates purpose-only. Tool names never\n * travel as PDLSS actions.\n */\n action: string | undefined;\n resource: string;\n purposeSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | undefined;\n actionSource: 'header' | 'meta' | 'tool_argument' | 'tool_gate' | 'transport_layer' | undefined;\n}\n\n/**\n * v2.5.0 — PDLSS field derivation for MCP requests.\n *\n * Purpose precedence:\n * - If `toolGate.purpose` is provided, it is authoritative (header/body ignored).\n * - Otherwise: header → body `_meta` → body `arguments` → `undefined`.\n *\n * Resource precedence:\n * - `toolGate.resource` if provided, else `requestPath`.\n *\n * Action precedence (3.1.0 — toolGate override added for\n * symmetry with purpose; 4.1.0 — the bare-tool-name fallback for\n * `tools/call` was removed):\n * - `toolGate.action` authoritative → header → body `_meta` → body\n * `arguments` → then: `undefined` for `tools/call` (purpose-only\n * evaluation — tool names are transport tokens, never PDLSS actions),\n * or the JSON-RPC method string for other methods (transport_layer).\n *\n * @param requestPath The HTTP request path (e.g. '/mcp'). Required.\n * @param toolGate Resolved per-tool config from `toolGates`, if present.\n */\nexport function mcpToPdlss(\n parsed: ParsedMcpRequest,\n requestPath: string,\n headerPurpose?: string,\n headerAction?: string,\n toolGate?: { purpose?: string; action?: string; resource?: string }\n): McpPdlssMapping {\n const resource = toolGate?.resource ?? requestPath;\n\n let purpose: string | undefined;\n let purposeSource: McpPdlssMapping['purposeSource'];\n if (toolGate?.purpose !== undefined) {\n purpose = toolGate.purpose;\n purposeSource = 'tool_gate';\n } else if (headerPurpose) {\n purpose = headerPurpose;\n purposeSource = 'header';\n } else if (parsed.purposeFromBody && parsed.purposeSourceFromBody) {\n purpose = parsed.purposeFromBody;\n purposeSource = parsed.purposeSourceFromBody;\n } else {\n purpose = undefined;\n purposeSource = undefined;\n }\n\n let action: string | undefined;\n let actionSource: McpPdlssMapping['actionSource'];\n if (toolGate?.action !== undefined) {\n action = toolGate.action;\n actionSource = 'tool_gate';\n } else if (headerAction) {\n action = headerAction;\n actionSource = 'header';\n } else if (parsed.actionFromBody && parsed.actionSourceFromBody) {\n action = parsed.actionFromBody;\n actionSource = parsed.actionSourceFromBody;\n } else if (parsed.method === 'tools/call') {\n // 4.1.0 fix: previously the bare tool name travelled as the PDLSS\n // action here, which guaranteed an action-policy deny for merchants\n // using the bare-string toolGate shorthand (tool names are transport\n // tokens, not policy vocabulary). With nothing configured or declared,\n // leave the action undeclared so evaluation is purpose-only.\n action = undefined;\n actionSource = undefined;\n } else {\n action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;\n actionSource = 'transport_layer';\n }\n\n return { purpose, action, resource, purposeSource, actionSource };\n}\n\n/**\n * Whether a method is enforced (calls verify-access + gates on the server\n * decision) by default. The MCP middleware uses this to split low-risk\n * handshake / introspection traffic (pass-through) from high-risk tool\n * execution (enforced). Per-tool / per-method overrides win over this default.\n *\n * - `initialize` / `notifications/initialized` → false (handshake must work for unregistered probes)\n * - `tools/list` / `prompts/list` / `resources/list` → false (introspection is public-surface)\n * - `ping` → false\n * - `resources/read` → true\n * - `tools/call` → true (default — overridable per-tool)\n * - everything else → true (least-privilege fallback)\n */\nexport function mcpDefaultEnforced(parsed: ParsedMcpRequest): boolean {\n if (parsed.isInitialize || parsed.method === 'notifications/initialized') return false;\n if (parsed.isIntrospection) return false;\n return true;\n}\n","/**\n * AstraSync Universal Verification Gateway — MCP middleware\n *\n * Express-shaped middleware tailored to the JSON-RPC body of an MCP\n * (Model Context Protocol) endpoint. Closes the cohort-3 gaps the default\n * `createMiddleware` couldn't:\n *\n * (a) **Body-aware gating**. All MCP traffic targets the same `/mcp` URL.\n * The default route-pattern matcher can't tell `initialize` (low risk)\n * from `tools/call start_checkout` (high risk). This middleware peels\n * the JSON-RPC body and applies a per-method risk tier.\n *\n * (b) **PDLSS mapping**. Derives purpose, action, and resource from\n * `toolGates` config and request context. See `transport/mcp-server.ts`\n * `mcpToPdlss()` for the exact mapping.\n *\n * (c) **Inner-hop dedupe**. Outbound responses set\n * `X-Astra-Verified-Hop` so a downstream REST endpoint that the tool\n * calls can skip a duplicate verify-access. The receiving REST\n * middleware checks `parseVerifiedHop` and skips when valid.\n *\n * (d) **Header-vs-body identity precedence**. Reads ASTRA-id from\n * `X-Astra-Id` first, body second. If both are present and disagree,\n * returns a structured 400 by default (configurable). Pre-fix,\n * integrators had to re-discover this on their own.\n *\n * Usage:\n *\n * ```typescript\n * import express from 'express';\n * import { createMcpMiddleware } from '@astrasyncai/verification-gateway/mcp';\n *\n * const app = express();\n * app.use(express.json());\n *\n * app.post(\n * '/mcp',\n * createMcpMiddleware({\n * apiBaseUrl: 'https://astrasync.ai/api',\n * apiKey: process.env.ASTRASYNC_API_KEY,\n * // Per-tool gates — `tools/call` is enforced by default; introspection /\n * // handshake pass through. A gated tool needs a PDLSS purpose (and ideally\n * // an action). Set a tool/method to `'observe'` to pass it through\n * // unenforced (still evaluated when `evaluateAlwaysIfCredentialed`).\n * toolGates: {\n * browse_catalog: { purpose: 'shopping', action: 'shopping.search' },\n * start_checkout: { purpose: 'shopping', action: 'shopping.purchase' },\n * health_check: 'observe',\n * },\n * }),\n * yourMcpServerHandler,\n * );\n * ```\n */\n\nimport type { Request, Response, NextFunction, RequestHandler } from 'express';\nimport type {\n GatewayConfig,\n VerificationResult,\n EnhancedVerificationResult,\n VerificationRequest,\n} from '../types';\nimport { verify, extractCredentials, recordDecision } from '../verify';\nimport { buildSdkObservedMetadata } from '../metadata-capture';\nimport { requiresHumanApproval, annotateApprovalRequired } from './approval-gate';\nimport { awaitStepUpApproval } from './stepup-poll';\nimport { prefetchWellKnown, getWellKnownUrls } from '../well-known';\nimport {\n parseMcpJsonRpc,\n mcpToPdlss,\n mcpDefaultEnforced,\n serializeVerifiedHop,\n parseVerifiedHop,\n isVerifiedHopValidFor,\n MCP_VERIFIED_HOP_HEADER,\n type ParsedMcpRequest,\n} from '../transport/mcp-server';\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n mcpRequest?: ParsedMcpRequest;\n // Declared here as well as in express.ts (identical type — the merged\n // interfaces are compatible) so this module typechecks standalone: the\n // grouped DTS build compiles each entry group as its own program, and\n // mcp.ts must not depend on express.ts being in the same one.\n agentVerification?: VerificationResult;\n }\n }\n}\n\n/**\n * Extended per-tool gate with optional PDLSS purpose + action + resource\n * overrides.\n *\n * When `purpose` is set, it is authoritative for that tool — the agent's\n * `X-Astra-Purpose` header is ignored. This lets the merchant declare what\n * semantic purpose each tool fulfils rather than trusting agent self-declaration.\n *\n * When `action` is set (symmetric with `purpose`), it is\n * authoritative over `X-Astra-Action` / body declarations, letting the\n * merchant pin a dotted-verb action (e.g. `shopping.search`) for a tool\n * whose callers would otherwise leave the action undeclared (purpose-only\n * evaluation — raw tool names never travel as PDLSS actions).\n *\n * When `resource` is set, it overrides the default (`req.path`) for that\n * tool's verify-access call — e.g. mapping `list_products` to `/api/catalog`.\n */\nexport interface ToolGateConfig {\n purpose?: string;\n action?: string;\n resource?: string;\n}\n\n/**\n * A tool/method gate is either a {@link ToolGateConfig} (enforced — calls\n * verify-access and gates on the server decision, using the declared PDLSS\n * purpose/action) or the literal `'observe'` (passed through unenforced;\n * still evaluated when `evaluateAlwaysIfCredentialed` is set). SDK 5.0.0\n * removed the access-level band, so a gate no longer carries a tier — its\n * only decision is enforce vs observe.\n */\nexport type ToolGate = ToolGateConfig | 'observe';\n\nexport interface McpMiddlewareOptions extends GatewayConfig {\n /**\n * Per-tool gating for `tools/call` invocations. Tools not listed are\n * enforced by default (`tools/call` calls verify-access and gates on the\n * server decision).\n *\n * A gated tool declares its PDLSS `purpose` (required: the backend rejects\n * gated calls with no resolvable purpose) and ideally a dotted-verb\n * `action`:\n * ```typescript\n * toolGates: {\n * list_products: { purpose: 'shopping',\n * action: 'shopping.search',\n * resource: '/api/catalog' },\n * start_checkout: { purpose: 'shopping',\n * action: 'shopping.purchase',\n * resource: '/api/checkout/*' },\n * health_check: 'observe', // pass through unenforced\n * }\n * ```\n *\n * Set a tool to `'observe'` to pass it through without enforcement (it is\n * still evaluated for the audit trail when `evaluateAlwaysIfCredentialed`\n * is set). A gated object form with no resolvable purpose fails fast with a\n * `PDLSS_PURPOSE_REQUIRED` 400 (the SDK never sends the raw tool name as\n * the PDLSS action).\n *\n * The action axis for non-tools/call MCP methods (`tools/list`,\n * `resources/list`, `prompts/list`, etc.) is the literal JSON-RPC method\n * name (e.g. `'tools/list'`). Merchants gating MCP traffic by action should\n * declare per-tool gates in `toolGates`, not endpoint-level `allowedActions`\n * — the latter applies to REST-style action values, not MCP method strings.\n */\n toolGates?: Record<string, ToolGate>;\n /**\n * Per-method override. By default introspection / handshake methods\n * (`tools/list`, `initialize`, `ping`, …) pass through and `tools/call` is\n * enforced; declare a method here to enforce it (object form, with a PDLSS\n * purpose) or set it to `'observe'` to pass it through. Matches by exact\n * JSON-RPC method string.\n */\n methodGates?: Record<string, ToolGate>;\n /**\n * What to do when the agent id supplied in the X-Astra-Id header\n * disagrees with the agent id in the JSON-RPC body\n * (`params._meta.astrasync.agentId` or `params.arguments.agent_id`).\n *\n * - `'reject'` (default) — return 400 `AGENT_ID_MISMATCH`. Safest.\n * - `'prefer-header'` — log + verify against the header value. Keeps\n * bodies that were authored before X-Astra-Id was the canonical\n * identity slot working.\n * - `'prefer-body'` — log + verify against the body value. Useful in\n * reverse-proxy setups that strip auth headers.\n */\n onAgentIdMismatch?: 'reject' | 'prefer-header' | 'prefer-body';\n /** Skip verification + dedupe entirely. For testing. */\n skip?: boolean;\n /** Custom denied handler. Defaults to a structured JSON-RPC error response. */\n onDenied?: (result: VerificationResult, req: Request, res: Response) => void;\n /**\n * If `true`, trust an inbound `X-Astra-Verified-Hop` header to skip\n * verify-access when it carries a recent marker for the resolved agent.\n *\n * **DEFAULT FLIPPED TO `false` in SDK 2.4.13.** The marker\n * is plaintext semicolon-delimited with no HMAC, so any client that sets\n * the header (with the right format + fresh timestamp + matching ASTRA-id)\n * skipped verify-access entirely. The per-process verify-access result\n * cache already dedupes legitimate repeat calls — this optimization\n * provided no additional savings, only a bypass surface.\n *\n * Set this to `true` ONLY if you control every upstream MCP hop that sets\n * the header AND you trust the network path between hops (mTLS, internal\n * VPC, etc.). For most installs, leave at the safe default.\n *\n * A future SDK release will either remove this option entirely OR add\n * HMAC signing to the marker.\n */\n trustVerifiedHop?: boolean;\n /** Window for accepting an upstream verified-hop marker. Default 60_000ms. */\n verifiedHopMaxAgeMs?: number;\n /**\n * Automatically record grant/deny decisions for every MCP call. Default\n * `true` — matches the express adapter.\n */\n recordDecisions?: boolean;\n /** Forward runtime challenge (default `true`). */\n enableRuntimeChallenge?: boolean;\n /**\n * Posture when the MCP middleware itself throws an internal error.\n * Default `'open'` for backward compatibility in SDK 2.4.13. Shadow logs\n * record what WOULD be denied with `failOnError: 'closed'` so merchants\n * can grep correlationId for impact analysis during the observation\n * window. Default flips to `'closed'` in a follow-up release.\n */\n failOnError?: 'open' | 'closed';\n}\n\n/**\n * Default MCP denied handler.\n *\n * Mirrors the express adapter — surfaces `failures[]`\n * and `correlationId` on the response so partners can render rich UX and\n * tie a denial to a log line. Also stamps `X-Astra-Gateway-Mode: enforced`\n * so the header reliably distinguishes gate-evaluated denials\n * from gate-skipped pass-throughs.\n */\n/**\n * Shared single-value HTTP header reader. Same shape used for\n * both `X-Astra-Purpose` and `X-Astra-Action` extraction (symmetric\n * fallback chain — see `mcpToPdlss` for the full precedence rule).\n * Returns the first value if the header repeats (Express turns repeated\n * headers into an array); returns undefined when the header is absent.\n */\nfunction readSingleHeader(value: string | string[] | undefined): string | undefined {\n if (typeof value === 'string') return value;\n if (Array.isArray(value)) return value[0];\n return undefined;\n}\n\nfunction dedupeFailures(result: VerificationResult): void {\n if (result.failures && result.failures.length > 1) {\n const seen = new Set<string>();\n result.failures = result.failures.filter((f) => {\n const key = `${f.dimension}|${f.message}|${(f as { guidance?: string }).guidance ?? ''}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n }\n if (result.denialReasons && result.denialReasons.length > 1) {\n result.denialReasons = [...new Set(result.denialReasons)];\n }\n}\n\nfunction defaultMcpDenied(result: VerificationResult, req: Request, res: Response): void {\n // JSON-RPC error envelope so MCP-aware clients render it correctly.\n const id = (req.body as Record<string, unknown> | undefined)?.id ?? null;\n\n // 4.0.0 BREAKING: the integration's OWN key was rejected — not the agent's\n // fault, not transient. 503 + a dedicated JSON-RPC code so MCP clients can\n // tell \"this server's verification layer is broken\" from their own denial.\n if (result.misconfigured) {\n res.setHeader('X-Astra-Gateway-Mode', 'enforced');\n res.status(503).json({\n jsonrpc: '2.0',\n id,\n error: {\n // -32002 → merchant verification misconfigured (see -32000/-32001 below)\n code: -32002,\n message:\n result.denialReasons?.[0] ?? \"This server's AstraSync integration is misconfigured\",\n data: {\n guidance: result.guidance,\n failures: result.failures,\n correlationId: result.correlationId,\n },\n },\n });\n return;\n }\n\n // Round-18 G4: see express.defaultOnDenied for full rationale on the\n // identity-vs-policy split → 401/403 mapping. Same logic here so MCP\n // clients get HTTP status semantics aligned with REST adapters.\n const status = !result.identityVerified ? 401 : 403;\n\n res.setHeader('X-Astra-Gateway-Mode', 'enforced');\n\n res.status(status).json({\n jsonrpc: '2.0',\n id,\n error: {\n // JSON-RPC error codes:\n // -32000 → unauthorized (no identity resolved)\n // -32001 → insufficient access (identity OK, policy denied)\n // -32002 → merchant verification misconfigured (handled above)\n code: !result.identityVerified ? -32000 : -32001,\n message: result.denialReasons?.[0] ?? 'Access denied',\n data: {\n guidance: result.guidance,\n // Round-10: aggregated per-dimension detail + correlation handle.\n failures: result.failures,\n correlationId: result.correlationId,\n stepUpApproval: result.stepUpApproval,\n // 3.6.0: terminal outcome of an opt-in hold-and-poll wait (denied /\n // expired / consumed / timeout). Absent when the feature is off.\n ...(result.stepUpOutcome && { stepUpOutcome: result.stepUpOutcome }),\n },\n },\n });\n}\n\n/**\n * Resolve whether a parsed MCP request is enforced, and the gate config to\n * apply. SDK 5.0.0 removed the access-level band — a gate's only decision is\n * enforce vs observe.\n *\n * Precedence:\n * 1. Non-tools/call without `methodGates` entry → pass through\n * (`discovery_default`). Discovery is open — agents must list tools\n * before invoking them.\n * 2. `methodGates[method]` — per-method override (`'observe'` or a config)\n * 3. `toolGates[toolName]` — per-tool override (tools/call only)\n * 4. `mcpDefaultEnforced` — built-in default (tools/call → enforced)\n *\n * The `source` field is logged for forensics / activity-feed attribution.\n */\nfunction resolveGate(\n parsed: ParsedMcpRequest,\n opts: {\n toolGates?: Record<string, ToolGate>;\n methodGates?: Record<string, ToolGate>;\n }\n): {\n enforce: boolean;\n config?: ToolGateConfig;\n source: 'toolGate' | 'methodGate' | 'tier' | 'discovery_default';\n} {\n if (!parsed.toolName) {\n const methodGate = opts.methodGates?.[parsed.method];\n if (methodGate !== undefined) {\n return methodGate === 'observe'\n ? { enforce: false, source: 'methodGate' }\n : { enforce: true, config: methodGate, source: 'methodGate' };\n }\n // Non-tools/call methods (handshake / introspection) pass through unless a\n // methodGate opts them in — preserves open discovery for unregistered probes.\n return { enforce: false, source: 'discovery_default' };\n }\n const toolGate = opts.toolGates?.[parsed.toolName];\n if (toolGate !== undefined) {\n return toolGate === 'observe'\n ? { enforce: false, source: 'toolGate' }\n : { enforce: true, config: toolGate, source: 'toolGate' };\n }\n // tools/call with no explicit gate → enforced by default.\n return { enforce: mcpDefaultEnforced(parsed), source: 'tier' };\n}\n\n/**\n * Create the MCP middleware. Attach AFTER `express.json()` — the body must\n * already be a parsed object.\n */\nexport function createMcpMiddleware(options: McpMiddlewareOptions): RequestHandler {\n const {\n toolGates,\n methodGates,\n onAgentIdMismatch = 'reject',\n skip = false,\n onDenied = defaultMcpDenied,\n trustVerifiedHop = false,\n verifiedHopMaxAgeMs,\n recordDecisions,\n enableRuntimeChallenge = true,\n failOnError = 'open',\n ...config\n } = options;\n\n // v2.5.0: fire-and-forget pre-fetch of well-known URLs\n if (config.apiBaseUrl) {\n prefetchWellKnown(config.apiBaseUrl).catch(() => {});\n }\n\n return async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n if (skip) return next();\n\n const parsed = parseMcpJsonRpc(req.body);\n if (!parsed) {\n // Not a JSON-RPC body. Either the route is misconfigured or the\n // client sent a non-MCP payload. We don't 400 here — let the actual\n // MCP server handler render its own error so the middleware doesn't\n // pre-empt valid framework error shapes.\n if (config.setPassThroughHeader) {\n // Round-10 (#49, O10): `unenforced` describes the GATE state, not\n // the downstream response. Renamed from the ambiguous\n // `pass-through` (which partners read as \"request succeeded\").\n res.setHeader('X-Astra-Gateway-Mode', 'unenforced');\n res.setHeader('X-Astra-Gateway-Reason', 'non-jsonrpc-body');\n }\n return next();\n }\n req.mcpRequest = parsed;\n\n // ── Identity reconciliation (defect d) ─────────────────────────────\n // Header value is the SDK-canonical slot; body slot exists for legacy\n // hand-written tool callers and the MCP `_meta.astrasync.agentId`\n // convention.\n const headerRaw = req.headers['x-astra-id'] ?? req.headers['x-astra-agentid'];\n const headerAstraId =\n typeof headerRaw === 'string'\n ? headerRaw\n : Array.isArray(headerRaw)\n ? headerRaw[0]\n : undefined;\n const bodyAstraId = parsed.agentIdFromBody;\n\n let effectiveAstraId: string | undefined;\n if (headerAstraId && bodyAstraId && headerAstraId !== bodyAstraId) {\n if (onAgentIdMismatch === 'reject') {\n const id = (req.body as Record<string, unknown>)?.id ?? null;\n res.status(400).json({\n jsonrpc: '2.0',\n id,\n error: {\n code: -32602,\n message: 'AGENT_ID_MISMATCH',\n data: {\n detail:\n 'The agent id in the X-Astra-Id header disagrees with params._meta.astrasync.agentId / params.arguments.agent_id. Reconcile before calling the tool — see https://astrasync.ai/docs/mcp-integration#identity-reconciliation.',\n headerAstraId,\n bodyAstraId,\n },\n },\n });\n return;\n }\n effectiveAstraId = onAgentIdMismatch === 'prefer-header' ? headerAstraId : bodyAstraId;\n } else {\n effectiveAstraId = headerAstraId ?? bodyAstraId;\n }\n\n // ── Inner-hop dedupe (defect c) ────────────────────────────────────\n // If a trusted upstream MCP hop already verified this exact ASTRA-id\n // a few ms ago, we accept the marker and skip a redundant verify-access\n // call. Only valid when the header is fresh AND matches the agent id\n // we resolved above.\n if (trustVerifiedHop && effectiveAstraId) {\n const hopRaw = req.headers[MCP_VERIFIED_HOP_HEADER.toLowerCase()];\n const hopValue =\n typeof hopRaw === 'string' ? hopRaw : Array.isArray(hopRaw) ? hopRaw[0] : undefined;\n const marker = parseVerifiedHop(hopValue);\n if (\n isVerifiedHopValidFor(marker, effectiveAstraId, {\n ...(verifiedHopMaxAgeMs !== undefined ? { maxAgeMs: verifiedHopMaxAgeMs } : {}),\n })\n ) {\n // Skip verify-access — pass straight through. Don't re-emit the\n // header since the MCP framework will issue the response (the\n // outer hop already tagged it for any further inner hops).\n return next();\n }\n }\n\n // ── Resolve gating ─────────────────────────────────────────────────\n const gateDecision = resolveGate(parsed, {\n toolGates,\n methodGates,\n });\n\n // ── Build verify-access request ────────────────────────────────────\n // Round-13 (R13-5) — credentials extraction hoisted above the\n // mcp-tier-none short-circuit (mirrors round-12 F9 express adapter\n // restructure). The `evaluateAlwaysIfCredentialed` flag lets the\n // caller turn unenforced tier-none calls into evaluated-not-enforced\n // calls when credentials are present, so the agent verification\n // result populates `req.agentVerification` for tier-aware handler\n // rendering (e.g. anonymous vs verified MCP tool listings).\n const credentials = extractCredentials(\n req.headers as Record<string, string | string[] | undefined>,\n req.query as Record<string, string | undefined>\n );\n // Override the SDK's default credential extraction with the resolved\n // ASTRA-id so the verify-access call sees the post-precedence value\n // (defect d). When neither header nor body supplied an id, the SDK's\n // anonymous-canonical-flow handles it.\n if (effectiveAstraId) credentials.astraId = effectiveAstraId;\n\n // Pass-through short-circuit. Default behaviour: skip verify-access for\n // observe / handshake / introspection (must work for unregistered\n // probes). With `evaluateAlwaysIfCredentialed: true` AND credentials\n // present, fall through to verify-access for the audit trail; the\n // post-verify gate-skipping branch below handles the rendering side.\n // Round-12 F9 parity (closes the round-12 deferral).\n const shouldEnforce = gateDecision.enforce;\n if (!shouldEnforce && (!config.evaluateAlwaysIfCredentialed || !credentials.astraId)) {\n if (config.setPassThroughHeader) {\n // Round-10 (#49, O10): `unenforced` describes the GATE state, not\n // the downstream response.\n res.setHeader('X-Astra-Gateway-Mode', 'unenforced');\n res.setHeader('X-Astra-Gateway-Reason', 'mcp-observe');\n }\n return next();\n }\n\n // Per-tool gate config (purpose + action + resource overrides).\n const gate = gateDecision.config;\n\n const headerPurpose = readSingleHeader(req.headers['x-astra-purpose']);\n const headerAction = readSingleHeader(req.headers['x-astra-action']);\n const pdlss = mcpToPdlss(\n parsed,\n req.path,\n headerPurpose,\n headerAction,\n gate ? { purpose: gate.purpose, action: gate.action, resource: gate.resource } : undefined\n );\n\n // Telemetry: debug log per resolution. Adoption tracking, support\n // triage, and the `default_*` / `transport_layer` decay signals\n // all read this. Both purpose and action emit their source so we\n // can spot which integration path each partner is using.\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.debug('[mcp-middleware] pdlss resolved', {\n purpose_source: pdlss.purposeSource,\n resolved_purpose: pdlss.purpose,\n action_source: pdlss.actionSource,\n resolved_action: pdlss.action,\n });\n }\n\n // 3.2.0 — clear \"purpose required\" error. We only reach here on the\n // ENFORCED path (the `!shouldEnforce` early-return is above). A gated\n // tool call with no resolvable PDLSS purpose (none on the toolGate, no\n // X-Astra-Purpose header, none in the body) would otherwise hit\n // verify-access's generic \"Invalid request data\" → surface to the agent\n // as an opaque `verify_access.api_error`. Fail fast with an actionable\n // message instead (purpose is required post-vocabulary-unification).\n if (!pdlss.purpose) {\n const id = (req.body as Record<string, unknown>)?.id ?? null;\n res.status(400).json({\n jsonrpc: '2.0',\n id,\n error: {\n code: -32602,\n message: 'PDLSS_PURPOSE_REQUIRED',\n data: {\n dimension: 'pdlss.purpose',\n detail:\n 'This tool is access-gated but the call declared no PDLSS purpose. Supply a bare-category purpose via the X-Astra-Purpose header or params._meta.astrasync.purpose, or have the merchant set the tool’s purpose in its toolGate config.',\n resolvedAction: pdlss.action,\n },\n },\n });\n return;\n }\n\n const counterpartyUrl =\n config.counterpartyUrl || `${req.protocol}://${req.get('host')}${req.path}`;\n const shouldRecordDecisions = recordDecisions !== false;\n\n const verificationRequest: VerificationRequest = {\n credentials,\n purpose: pdlss.purpose,\n action: pdlss.action,\n resource: pdlss.resource,\n // Round-12 (F19): mark transport protocol separately from intent.\n // The MCP middleware always sets this to 'mcp'; non-MCP callers\n // leave it unset (server-side default is 'rest').\n invocationProtocol: 'mcp',\n createSession: shouldRecordDecisions,\n counterpartyUrl,\n counterpartyType: config.counterpartyType || 'mcp_server',\n enableRuntimeChallenge,\n callerMetadata: {\n sourceIp: req.ip,\n userAgent: req.headers['user-agent'] as string | undefined,\n host: req.headers.host as string | undefined,\n // Capture EVERY inbound header (parity with the express adapter —\n // MCP-only merchants have no edge to do it upstream) plus the\n // CDN-derived connection block. Local string work, secrets\n // stripped; out of the verify cache key so it doesn't defeat\n // caching.\n observedMetadata: buildSdkObservedMetadata(req.headers),\n },\n };\n let result = await verify(config, verificationRequest);\n\n req.agentVerification = result;\n let sessionId = (result as EnhancedVerificationResult).sessionId;\n\n // 3.6.0 hold-and-poll (opt-in via stepUpMaxWaitMs) — see express.ts for\n // the full rationale. On 'approved' the same request is re-verified\n // once, cache-bypassed, so the backend redeems the single-use approval.\n if (\n config.stepUpMaxWaitMs !== undefined &&\n result.policyAllowed &&\n requiresHumanApproval(result) &&\n result.stepUpApproval?.pollUrl\n ) {\n const outcome = await awaitStepUpApproval(config, result.stepUpApproval);\n res.setHeader('X-Astra-Step-Up', outcome);\n if (outcome === 'approved') {\n result = await verify(config, verificationRequest, { bypassCache: true });\n req.agentVerification = result;\n sessionId = (result as EnhancedVerificationResult).sessionId;\n } else {\n result.stepUpOutcome = outcome;\n }\n }\n\n // v2.3.9 (defect #30): denied verifications short-circuit BEFORE the\n // gate-level comparison. See express.ts for the full rationale —\n // mirroring it here so the MCP path inherits the same belt-and-braces\n // protection against access-level/help-payload conflation.\n // 4.5.0: gate on the SERVER decision only (`policyAllowed`) — anonymous\n // callers allowed by an audit/allow endpoint policy are honestly\n // identity-unverified; see express.ts.\n if (!result.policyAllowed) {\n if (shouldRecordDecisions && sessionId) {\n recordDecision(config, sessionId, 'denied', result.denialReasons?.[0]).catch(() => {});\n }\n dedupeFailures(result);\n onDenied(result, req, res);\n return;\n }\n\n // F2 direct-path fail-closed: a step-up / requires-approval outcome\n // (policyAllowed:true, allowed-but-escalate) cannot be completed\n // autonomously — deny and surface stepUpApproval polling info.\n if (requiresHumanApproval(result)) {\n annotateApprovalRequired(result);\n if (shouldRecordDecisions && sessionId) {\n recordDecision(config, sessionId, 'denied', result.denialReasons?.[0]).catch(() => {});\n }\n dedupeFailures(result);\n onDenied(result, req, res);\n return;\n }\n\n // Round-13 (R13-5): evaluation-without-enforcement parity with the\n // express F9 adapter. When the route is `none` but we ran\n // verify-access because `evaluateAlwaysIfCredentialed: true` AND\n // credentials were present, skip the gates and call next() — the\n // result is on `req.agentVerification` for the MCP handler to\n // render tier-aware responses (e.g. anonymous vs verified\n // tool-listing on the same `/mcp` endpoint).\n if (!shouldEnforce) {\n if (config.setPassThroughHeader) {\n res.setHeader('X-Astra-Gateway-Mode', 'enforced');\n res.setHeader('X-Astra-Gateway-Reason', 'evaluated-not-enforced');\n }\n if (shouldRecordDecisions && sessionId) {\n recordDecision(config, sessionId, 'granted').catch(() => {});\n }\n return next();\n }\n\n // The access decision is the server's (identityVerified / policyAllowed,\n // denied above) — resting on a verified ID + runtime challenge + PDLSS\n // match + trust score. The per-tool gate only decides WHETHER a tool is\n // enforced at all (`shouldEnforce`, above); there is no client-side tier\n // veto (the access-level band was removed in SDK 5.0.0).\n\n // ── Tag the outbound response with X-Astra-Verified-Hop ────────────\n // Inner-hop REST endpoints called by this tool can read the header\n // and skip a duplicate verify-access. We set it on the response so\n // downstream proxies see it; tools that perform fetch() to an inner\n // hop should also forward it themselves (the MCP server framework\n // controls outbound calls — out of scope here).\n if (effectiveAstraId) {\n res.setHeader(\n MCP_VERIFIED_HOP_HEADER,\n serializeVerifiedHop({\n astraId: effectiveAstraId,\n ...(sessionId ? { sessionId } : {}),\n checkedAt: Date.now(),\n })\n );\n }\n\n if (shouldRecordDecisions && sessionId) {\n recordDecision(config, sessionId, 'granted').catch(() => {});\n }\n // v2.3.8 (defect #26): relay X-Astra-Unverified-Warning when the\n // server returned an audit-mode advisory.\n const enhancedResult = result as EnhancedVerificationResult;\n if (enhancedResult.warningHeader) {\n res.setHeader(enhancedResult.warningHeader.name, enhancedResult.warningHeader.value);\n }\n next();\n } catch (error) {\n // Audit F-A1-06: shadow-mode logging for fail-closed default migration.\n // Mirror of the express adapter pattern. Default 'open' for backward\n // compat in SDK 2.4.13; shadow logs let merchants grep correlationId\n // for impact analysis before the default flips in a follow-up release.\n const errorClass = error instanceof Error ? error.constructor.name : typeof error;\n const correlationId =\n (req.headers['x-request-id'] as string | undefined) ||\n (req.headers['x-correlation-id'] as string | undefined) ||\n `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n // eslint-disable-next-line no-console\n console.error('[VerificationGateway/MCP] Middleware error:', error);\n // eslint-disable-next-line no-console\n console.warn(\n `[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? 'unknown'} correlationId=${correlationId}`\n );\n\n if (failOnError === 'closed') {\n const result: VerificationResult = {\n identityVerified: false,\n policyAllowed: false,\n denialReasons: [`MCP middleware internal error: ${errorClass}`],\n failures: [\n {\n dimension: 'middleware.internal_error',\n message: `Middleware threw ${errorClass} — failing closed`,\n },\n ],\n verifiedAt: new Date(),\n correlationId,\n };\n const catchUrls = config.apiBaseUrl\n ? await getWellKnownUrls(config.apiBaseUrl).catch(() => undefined)\n : undefined;\n if (catchUrls) {\n result.guidance = {\n message: `Middleware threw ${errorClass} — failing closed`,\n registrationUrl: catchUrls.registrationUrl,\n documentationUrl: catchUrls.documentationUrl,\n };\n }\n dedupeFailures(result);\n return onDenied(result, req, res);\n }\n\n next();\n }\n };\n}\n\n// Re-export the helpers so consumers can mix-and-match (e.g. read the\n// verified-hop header themselves on a custom inner-hop handler).\nexport {\n parseMcpJsonRpc,\n mcpToPdlss,\n mcpDefaultEnforced,\n serializeVerifiedHop,\n parseVerifiedHop,\n isVerifiedHopValidFor,\n MCP_VERIFIED_HOP_HEADER,\n};\nexport type { ParsedMcpRequest } from '../transport/mcp-server';\n"],"mappings":"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","names":["has","result"]}
|