@astrasyncai/verification-gateway 2.4.9 → 2.4.10
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/dist/adapters/express.js +53 -17
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +53 -17
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.js +35 -13
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +35 -13
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.js +53 -17
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +53 -17
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.js +35 -13
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +35 -13
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/browser/background.js +36 -14
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +36 -14
- package/dist/browser/background.mjs.map +1 -1
- package/dist/cursor/extension.js +36 -14
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +36 -14
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/gateway/gateway.js +36 -14
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +36 -14
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/index.js +54 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +54 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/access-levels.ts","../../src/version.ts","../../src/verify.ts","../../src/transport/mcp-server.ts","../../src/adapters/mcp.ts"],"sourcesContent":["/**\n * AstraSync Universal Verification Gateway - Access Level Definitions\n *\n * Defines the hierarchy and capabilities of each access level.\n *\n * v2.3.9 (defect #30): renamed `'guidance'` band → `'restricted'`. See\n * `types.ts` AccessLevel for the rationale (value-name collision with the\n * `guidance: {...}` help-payload object on VerificationResult).\n */\n\nimport type { AccessLevel, TrustLevel } from './types';\n\n/**\n * Access level hierarchy (higher number = more access)\n */\nexport const ACCESS_LEVEL_HIERARCHY: Record<AccessLevel, number> = {\n none: 0,\n restricted: 1,\n 'read-only': 2,\n standard: 3,\n full: 4,\n internal: 5,\n};\n\n/**\n * Access level descriptions for UI\n */\nexport const ACCESS_LEVEL_DESCRIPTIONS: Record<AccessLevel, string> = {\n none: 'No access - credentials required',\n restricted: 'Restricted access - registration prompt only',\n 'read-only': 'Read-only access - can browse but not modify',\n standard: 'Standard access - normal operations per PDLSS policy',\n full: 'Full access - all operations for high-trust agents',\n internal: 'Internal access - organization member privileges',\n};\n\n/**\n * Default trust score thresholds for access levels\n */\nexport const DEFAULT_TRUST_THRESHOLDS: Record<AccessLevel, number> = {\n none: 0,\n restricted: 0,\n 'read-only': 20,\n standard: 40,\n full: 70,\n internal: 0, // Internal is based on org membership, not score\n};\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\n/**\n * Check if access level A is greater than or equal to access level B\n */\nexport function hasMinimumAccess(actual: AccessLevel, required: AccessLevel): boolean {\n return ACCESS_LEVEL_HIERARCHY[actual] >= ACCESS_LEVEL_HIERARCHY[required];\n}\n\n/**\n * Get the highest access level for a given trust score\n */\nexport function getAccessLevelForScore(\n trustScore: number,\n thresholds: Record<AccessLevel, number> = DEFAULT_TRUST_THRESHOLDS\n): AccessLevel {\n if (trustScore >= thresholds.full) return 'full';\n if (trustScore >= thresholds.standard) return 'standard';\n if (trustScore >= thresholds['read-only']) return 'read-only';\n return 'restricted';\n}\n\n/**\n * Determine access level from verification result.\n *\n * v2.3.9 (defect #30): unverified callers now return `'none'` (was\n * `'guidance'`). Denials grant zero — never a positive band.\n */\nexport function determineAccessLevel(\n verified: boolean,\n trustScore: number,\n isOrgMember: boolean,\n customThresholds?: Partial<Record<AccessLevel, number>>\n): AccessLevel {\n if (!verified) {\n return 'none';\n }\n\n if (isOrgMember) {\n return 'internal';\n }\n\n const thresholds = {\n ...DEFAULT_TRUST_THRESHOLDS,\n ...customThresholds,\n };\n\n return getAccessLevelForScore(trustScore, thresholds);\n}\n\n/**\n * Access capabilities per level\n */\nexport interface AccessCapabilities {\n canRead: boolean;\n canWrite: boolean;\n canDelete: boolean;\n canAdmin: boolean;\n canAccessInternal: boolean;\n maxTransactionValue?: number;\n allowedPurposes?: string[];\n}\n\n/**\n * Get capabilities for an access level\n */\nexport function getCapabilities(accessLevel: AccessLevel): AccessCapabilities {\n switch (accessLevel) {\n case 'none':\n return {\n canRead: false,\n canWrite: false,\n canDelete: false,\n canAdmin: false,\n canAccessInternal: false,\n };\n case 'restricted':\n return {\n canRead: false,\n canWrite: false,\n canDelete: false,\n canAdmin: false,\n canAccessInternal: false,\n };\n case 'read-only':\n return {\n canRead: true,\n canWrite: false,\n canDelete: false,\n canAdmin: false,\n canAccessInternal: false,\n };\n case 'standard':\n return {\n canRead: true,\n canWrite: true,\n canDelete: false,\n canAdmin: false,\n canAccessInternal: false,\n };\n case 'full':\n return {\n canRead: true,\n canWrite: true,\n canDelete: true,\n canAdmin: false,\n canAccessInternal: false,\n };\n case 'internal':\n return {\n canRead: true,\n canWrite: true,\n canDelete: true,\n canAdmin: true,\n canAccessInternal: true,\n };\n default:\n return {\n canRead: false,\n canWrite: false,\n canDelete: false,\n canAdmin: false,\n canAccessInternal: false,\n };\n }\n}\n","/**\n * Round-13 (F14 closure / R13-7): 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. Round-13 manual bump\n * is fine — bumping both in the release-ceremony commit keeps them\n * lockstep.\n */\nexport const SDK_VERSION = '2.4.9';\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 type {\n GatewayConfig,\n AgentCredentials,\n VerificationRequest,\n VerificationResult,\n VerifiedAgent,\n VerifiedDeveloper,\n VerifiedOrganization,\n GuidanceInfo,\n AccessLevel,\n EnhancedVerificationResult,\n TokenGuidance,\n RuntimeChallengeResult,\n} from './types';\nimport { getTrustLevel, ACCESS_LEVEL_HIERARCHY } from './access-levels';\nimport { SDK_VERSION } from './version';\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 // v2.3.9 (defect #30): default for unconfigured callers is `'none'` (no\n // access). Pre-rename this defaulted to `'guidance'`, which combined with\n // a route gated at `'guidance'` to silently let unverified traffic\n // through (`hasMinimumAccess('guidance', 'guidance') === true`).\n defaultAccessLevel: 'none',\n // minTrustScore + minTrustScoreForFull deprecated in v2.3.0 — server decides.\n cacheTtl: 300, // 5 minutes\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/** One-shot guard for v2.3.0 deprecation warning. */\nlet deprecationWarningShown = false;\n\nasync function performInitCheck(apiBaseUrl: string, debug?: boolean): 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 fetch(probeUrl, { method: 'HEAD' });\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.startsWith('text/html')) {\n console.warn(\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 `Set disableInitChecks: true on GatewayConfig to silence this warning.`\n );\n } else if (debug) {\n console.log(\n `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`\n );\n }\n } catch (err) {\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 credentials\n */\nfunction getCacheKey(credentials: AgentCredentials): string {\n return `${credentials.astraId || ''}-${credentials.apiKey || ''}-${credentials.jwt || ''}`;\n}\n\n/**\n * Check if cached result is still valid\n */\nfunction getCachedResult(credentials: AgentCredentials): VerificationResult | null {\n const key = getCacheKey(credentials);\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 */\nfunction cacheResult(\n credentials: AgentCredentials,\n result: VerificationResult,\n ttlSeconds: number\n): void {\n const key = getCacheKey(credentials);\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 // 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 credentials.astraId = Array.isArray(astraIdHeader) ? astraIdHeader[0] : astraIdHeader;\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 const authHeader = headers['authorization'] || headers['Authorization'];\n if (authHeader) {\n const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;\n credentials.authorizationHeader = authValue;\n\n if (authValue.startsWith('Bearer ')) {\n credentials.jwt = authValue.slice(7);\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. Round-10 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 */\ntype GuidanceSource = 'no_credentials' | 'api_error';\n\n/**\n * Create guidance response for unverified agents or for API-error fallback.\n *\n * Round-10 (#47, O5): split source 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: { source?: GuidanceSource; correlationId?: string } = {}\n): VerificationResult {\n const source = options.source ?? 'no_credentials';\n const isApiError = source === 'api_error';\n\n const guidance: GuidanceInfo = isApiError\n ? {\n message:\n 'Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.',\n registrationUrl: `${config.apiBaseUrl.replace('/api', '')}/register`,\n documentationUrl: `${config.apiBaseUrl.replace('/api', '')}/docs/agent-access`,\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: `${config.apiBaseUrl.replace('/api', '')}/register`,\n documentationUrl: `${config.apiBaseUrl.replace('/api', '')}/docs/agent-access`,\n steps: [\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 };\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 // v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.\n // Adapters additionally short-circuit on `!identityVerified ||\n // !policyAllowed` before the gate check, but the access level still has\n // to be honest at the data layer so downstream consumers (SDK adapters\n // in other languages, custom integrations) inherit the correct\n // semantics.\n accessLevel: 'none',\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: isApiError\n ? [\n {\n dimension: 'verify_access.api_error',\n message: reason ?? 'Verification temporarily unavailable',\n guidance: guidance.message,\n },\n ]\n : undefined,\n correlationId: options.correlationId,\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 /**\n * Server-decided access level. Read verbatim — do NOT remap client-side.\n * The server resolves this from endpoint policy + agent trust score using\n * the canonical thresholds (see backend `apps/backend/src/utils/access-levels.ts`).\n */\n accessLevel?: AccessLevel;\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 validUntil?: string;\n proofType: 'reference' | 'zkp';\n proof: string;\n }>;\n };\n error?: string;\n /**\n * Round-10 (#47, O5): 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 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 purpose: requestData.purpose || 'general',\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\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. The backend's authenticate middleware\n // accepts either a JWT or an API key (starts with kya_) via `Authorization: Bearer <token>`.\n // Credential-supplied auth header (e.g. the agent's own token) takes priority.\n if (credentials.authorizationHeader) {\n headers['Authorization'] = credentials.authorizationHeader;\n } else if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n }\n // Legacy header kept for compatibility with any middleware that reads it directly.\n if (config.apiKey) {\n headers['X-API-Key'] = config.apiKey;\n }\n\n try {\n const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\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 access: {\n allowed: false,\n accessLevel: 'none',\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 // 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 };\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 * Main verification function\n */\nexport async function verify(\n config: GatewayConfig,\n request: VerificationRequest\n): Promise<VerificationResult> {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n\n // One-time init self-test — fire-and-forget, never blocks verify().\n if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {\n void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug);\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-level decisions ' +\n '(the SDK reads access.accessLevel from the verify-access response). 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\n if (mergedConfig.cacheTtl && mergedConfig.cacheTtl > 0) {\n const cached = getCachedResult(request.credentials);\n if (cached) {\n if (mergedConfig.debug) {\n console.log('[VerificationGateway] Returning cached result');\n }\n return cached;\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 // 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 return createGuidanceResponse(mergedConfig, apiResponse.error, {\n source: 'api_error',\n correlationId: (apiResponse as { correlationId?: string }).correlationId,\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 // v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.\n // Pre-rename this hardcoded `'guidance'`, which conflated with the\n // colocated `guidance: {...}` help-payload object below and let\n // denied requests pass any route gated at `'guidance'` because\n // `hasMinimumAccess('guidance', 'guidance') === true`. Adapters now\n // ALSO short-circuit on `!identityVerified || !policyAllowed` before\n // the gate check — belt-and-braces.\n accessLevel: 'none',\n denialReasons:\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: `${mergedConfig.apiBaseUrl?.replace('/api', '')}/register`,\n documentationUrl: `${mergedConfig.apiBaseUrl?.replace('/api', '')}/docs/pdlss`,\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 // v2.3.10 (defect #34, round-4): anonymous traffic has no session →\n // correlationId is the linking key for paired local_override events.\n correlationId: (apiResponse as Record<string, unknown>).correlationId 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 };\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 // Server is the single source of truth for access level. SDK reads\n // apiResponse.access.accessLevel verbatim — no client-side trust-score remap.\n // Fallback to 'standard' if the server response is missing the field (older\n // backend without the v2.3.0 contract); it covers the verified-access case.\n const accessLevel: AccessLevel = apiResponse.access?.accessLevel ?? 'standard';\n\n const result: EnhancedVerificationResult = {\n // Round-18 G4: backend allowed access. Identity is verified (we resolved\n // the caller to an agent) and policy passed all gates. Read idVerified\n // from verificationContext for symmetry with the deny branch; default true\n // on success path since `access.allowed === true` implies identity was\n // resolvable (anonymous-allow paths flow through createGuidanceResponse).\n identityVerified:\n (apiResponse.verificationContext as { idVerified?: boolean } | undefined)?.idVerified !==\n false,\n policyAllowed: true,\n accessLevel,\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 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 };\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 result.accessLevel = 'none';\n result.denialReasons = result.recommendationReasons || [\n 'Access denied by AstraSync recommendation',\n ];\n if (result.runtimeChallenge) {\n result.guidance = {\n message: `Verification failed: ${result.runtimeChallenge.reason || 'runtime challenge failed'}`,\n registrationUrl: `${mergedConfig.apiBaseUrl?.replace('/api', '')}/register`,\n documentationUrl: `${mergedConfig.apiBaseUrl?.replace('/api', '')}/docs/runtime-challenge`,\n };\n }\n } else if (result.recommendation === 'step_up_required') {\n result.requiresStepUp = true;\n if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY['read-only']) {\n result.accessLevel = 'read-only';\n }\n result.denialReasons = result.recommendationReasons || ['Step-up verification required'];\n }\n\n // Cache the result (skip caching denials — agent may fix challenge endpoint and retry)\n if (mergedConfig.cacheTtl && mergedConfig.cacheTtl > 0 && result.recommendation !== 'deny') {\n cacheResult(request.credentials, result, mergedConfig.cacheTtl);\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 */\n/**\n * v2.3.9 (defect #34): optional override metadata. Set when the SDK's\n * local enforcement (toolGate / methodGate / trustScore floor) rejected\n * a request the SERVER had granted. Backend emits a distinct\n * `verification.local_override` event so the activity feed surfaces the\n * divergence as a separate row.\n */\nexport interface DecisionOverride {\n overriddenBy: 'toolGate' | 'methodGate' | 'trustScore' | 'other';\n toolName?: string;\n requestedLevel?: AccessLevel;\n grantedLevel?: AccessLevel;\n}\n\nexport async function recordDecision(\n config: GatewayConfig,\n sessionId: string,\n decision: 'granted' | 'denied',\n reason?: string,\n override?: DecisionOverride\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 fetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n decision,\n reason,\n ...(override && {\n overriddenBy: override.overriddenBy,\n toolName: override.toolName,\n requestedLevel: override.requestedLevel,\n grantedLevel: override.grantedLevel,\n }),\n }),\n }).catch(() => {\n /* fire-and-forget */\n });\n}\n\n/**\n * v2.3.10 (defect #34, round-4): record a SDK-side local override for an\n * anonymous verify-access response. Anonymous traffic has no session row, so\n * `recordDecision` (above) doesn't apply — but we still need to surface the\n * dashboard-vs-runtime divergence (e.g. server granted with audit warning\n * but local toolGate floor denied) on the activity feed.\n *\n * Backend ties the resulting `verification.local_override` event back to the\n * original `verification.unverified_audit` event via `correlationId`. The\n * endpoint is sessionless — see the docstring on the backend route for the\n * abuse-mitigation rationale (rate-limited per IP).\n *\n * Fire-and-forget — errors are silently swallowed.\n */\nexport async function recordAnonymousLocalOverride(\n config: GatewayConfig,\n correlationId: string,\n override: DecisionOverride,\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 fetch(`${config.apiBaseUrl}/agents/verify-access/local-override`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n correlationId,\n reason,\n overriddenBy: override.overriddenBy,\n toolName: override.toolName,\n requestedLevel: override.requestedLevel,\n grantedLevel: override.grantedLevel,\n }),\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 fetch(\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 minAccessLevel: 'none' | 'restricted' | 'read-only' | 'standard' | 'full' | 'internal';\n minTrustScore?: number;\n requiredPurposes?: string[];\n allowedPurposes?: string[];\n allowedJurisdictions?: string[];\n maxDuration?: number;\n maxTransactionValue?: number;\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): Promise<VerificationResult> {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n const result = await verify(mergedConfig, { ...request, createSession: true });\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 counterpartyUrl: string;\n counterpartyType?: string;\n sourceIp?: string;\n userAgent?: string;\n requestPath?: string;\n requestMethod?: string;\n }\n): Promise<void> {\n const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl!;\n\n await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {\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 * 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 fetch(`${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 * Round-18 G4: 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 accessLevel: AccessLevel;\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 accessLevel: result.accessLevel,\n reason: result.denialReasons?.[0],\n };\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 * - `mcpRiskTier(parsed)` — recommended `minAccessLevel` per method\n * so a single MCP middleware can split\n * `initialize` / `tools/list` (low gate)\n * from `tools/call` (high gate).\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\nimport type { AccessLevel } from '../types';\n\n/**\n * Header carrying upstream verify-access proof so an inner-hop REST endpoint\n * can dedupe (skip verify-access when the same ASTRA-id was already verified\n * a few ms earlier). Value 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 a dedupe\n * advisory. Pair with the existing X-Astra-Id auth.\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/**\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 * Round-12 (F19) + round-13 (R13-1): 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 `'mcp_invoke'`.\n */\n purposeFromBody?: string;\n /** Which body location resolved `purposeFromBody`. */\n purposeSourceFromBody?: 'meta' | 'tool_argument';\n /**\n * Round-13 (R13-2): 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 (round-13 R13-1, R13-2). 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. Doc-stable so\n * dashboards and audits can correlate consistently across cohort-3 partners.\n *\n * Rules:\n * - `purpose` is always `mcp_invoke` for MCP traffic — sets the high-level\n * PDLSS bucket.\n * - `action` is the JSON-RPC method, optionally `:tool_name` suffixed for\n * `tools/call`. Lets PDLSS allowlist specific tools.\n * - `resource` is `mcp:tool/<name>` for `tools/call`, `mcp:method/<method>`\n * otherwise. Lets PDLSS scope on tool identity.\n */\nexport interface McpPdlssMapping {\n purpose: string;\n action: string;\n resource: string;\n /**\n * Round-13 (R13-1 / R13-2): the resolution channel for purpose and\n * action — disjoint enums sharing the same structure per\n * `feedback_symmetric_fallbacks_for_symmetric_concepts.md`. Adapters\n * log both at debug level so partners can confirm header / body\n * pass-through, support can triage tickets, and we can watch the\n * `default_*` / `transport_layer` decay over time as integrations\n * mature.\n *\n * Round-12 (F19) used a narrower `purposeSource: 'header' |\n * 'tool_argument' | 'default_mcp_invoke'` — round-13 widens to also\n * carry the `_meta` source distinct from `tool_argument`, so the round-13\n * R13-1 fallback (which now reads both `_meta.astrasync.purpose` AND\n * `params.arguments.purpose`) can report WHICH body location resolved.\n */\n purposeSource: 'header' | 'meta' | 'tool_argument' | 'default_mcp_invoke';\n actionSource: 'header' | 'meta' | 'tool_argument' | 'transport_layer';\n}\n\n/**\n * Round-13 (R13-1 + R13-2) — canonical precedence chain documented ONCE,\n * applied identically to both purpose and action. Per\n * `feedback_symmetric_fallbacks_for_symmetric_concepts.md` — drift\n * between two paired concepts generates the same support-ticket class\n * repeatedly, so the resolution order is:\n *\n * 1. `X-Astra-<concept>` HTTP header (caller's explicit override)\n * 2. `params._meta.astrasync.<concept>` body field (canonical SDK location)\n * 3. `params.arguments.<concept>` body field (legacy / conventional)\n * 4. Transport-layer default:\n * purpose → 'mcp_invoke'\n * action → '<method>:<toolName>' (or just '<method>')\n *\n * The header tier is the adapter's job (it has `req.headers` access);\n * this function takes the resolved `headerPurpose` / `headerAction`\n * inputs and combines them with the body extraction already done in\n * `parseMcpJsonRpc`. The discriminator is reported on `purposeSource` /\n * `actionSource` for debug logging.\n */\nexport function mcpToPdlss(\n parsed: ParsedMcpRequest,\n headerPurpose?: string,\n headerAction?: string\n): McpPdlssMapping {\n const resource = parsed.toolName ? `mcp:tool/${parsed.toolName}` : `mcp:method/${parsed.method}`;\n\n // Purpose resolution: header > body (_meta, then tool_argument) > default\n let purpose: string;\n let purposeSource: McpPdlssMapping['purposeSource'];\n 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 = 'mcp_invoke';\n purposeSource = 'default_mcp_invoke';\n }\n\n // Action resolution: identical chain, transport-layer default differs\n let action: string;\n let actionSource: McpPdlssMapping['actionSource'];\n 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 {\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 * Recommended minimum access level per method type. The MCP middleware uses\n * this to split low-risk handshake / introspection traffic from high-risk\n * tool execution — defect (a) from the cohort-3 review.\n *\n * - `initialize` / `notifications/initialized` → `none` (handshake must work for unregistered probes)\n * - `tools/list` / `prompts/list` / `resources/list` → `none` (introspection is public-surface)\n * - `ping` → `none`\n * - `resources/read` → `read-only`\n * - `tools/call` → `standard` (default — overridable per-tool)\n * - everything else → `standard` (least-privilege fallback)\n */\nexport function mcpRiskTier(parsed: ParsedMcpRequest): AccessLevel {\n if (parsed.isInitialize || parsed.method === 'notifications/initialized') return 'none';\n if (parsed.isIntrospection) return 'none';\n if (parsed.method === 'resources/read') return 'read-only';\n if (parsed.isToolCall) return 'standard';\n return 'standard';\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**. Forwards `purpose=mcp_invoke`,\n * `action=method[:tool]`, `resource=mcp:tool/<name>` so audit traces\n * are stable across cohort-3 partners. See `transport/mcp-server.ts`\n * 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 * // Optional per-tool overrides — tools not listed get the default tier\n * // from `mcpRiskTier` (`tools/call` → 'standard').\n * toolGates: {\n * start_checkout: 'standard',\n * confirm_purchase: 'full',\n * browse_catalog: 'read-only',\n * },\n * }),\n * yourMcpServerHandler,\n * );\n * ```\n */\n\nimport type { Request, Response, NextFunction, RequestHandler } from 'express';\nimport type {\n GatewayConfig,\n AccessLevel,\n VerificationResult,\n EnhancedVerificationResult,\n} from '../types';\nimport {\n verify,\n extractCredentials,\n recordDecision,\n recordAnonymousLocalOverride,\n} from '../verify';\nimport { hasMinimumAccess } from '../access-levels';\nimport {\n parseMcpJsonRpc,\n mcpToPdlss,\n mcpRiskTier,\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 }\n }\n}\n\nexport interface McpMiddlewareOptions extends GatewayConfig {\n /**\n * Per-tool override for the minimum access level. Tools not listed inherit\n * the default tier from `mcpRiskTier` (`tools/call` → `'standard'`). Use\n * for high-risk tools that demand `'full'` or low-risk read-only tools.\n */\n toolGates?: Record<string, AccessLevel>;\n /**\n * Per-method override (e.g. tighten `tools/list` to `'read-only'` if you\n * don't want unregistered probes seeing your tool catalogue). Matches by\n * exact JSON-RPC method string.\n */\n methodGates?: Record<string, AccessLevel>;\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 `false`, don't trust an inbound `X-Astra-Verified-Hop` header (always\n * call verify-access). Default `true` — recommended for inner-hop endpoints\n * called by your own MCP tools.\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\n/**\n * Default MCP denied handler.\n *\n * Round-10 (#47, O5): 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 * (#49/O10) so the header reliably distinguishes gate-evaluated denials\n * from gate-skipped pass-throughs.\n */\n/**\n * Round-13: 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 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 // 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 code: !result.identityVerified ? -32000 : -32001,\n message: result.denialReasons?.[0] ?? 'Access denied',\n data: {\n accessLevel: result.accessLevel,\n guidance: result.guidance,\n // Round-10: aggregated per-dimension detail + correlation handle.\n failures: result.failures,\n correlationId: result.correlationId,\n },\n },\n });\n}\n\n/**\n * Resolve the effective minimum access level for a parsed MCP request.\n * Order: per-tool override → per-method override → tier-default from\n * `mcpRiskTier`.\n *\n * v2.3.9 (defect #34) returns the SOURCE of the resolution alongside the\n * level so the gate-failure site can attribute a local override correctly\n * (`toolGate` / `methodGate` / `tier`) when reporting back to the\n * activity feed.\n */\nfunction resolveMinAccessLevel(\n parsed: ParsedMcpRequest,\n opts: { toolGates?: Record<string, AccessLevel>; methodGates?: Record<string, AccessLevel> }\n): { level: AccessLevel; source: 'toolGate' | 'methodGate' | 'tier' } {\n if (parsed.toolName && opts.toolGates && opts.toolGates[parsed.toolName] !== undefined) {\n return { level: opts.toolGates[parsed.toolName], source: 'toolGate' };\n }\n if (opts.methodGates && opts.methodGates[parsed.method] !== undefined) {\n return { level: opts.methodGates[parsed.method], source: 'methodGate' };\n }\n return { level: mcpRiskTier(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 = true,\n verifiedHopMaxAgeMs,\n recordDecisions,\n enableRuntimeChallenge = true,\n ...config\n } = options;\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 { level: minAccessLevel, source: gateSource } = resolveMinAccessLevel(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 // Risk tier `none` short-circuit. Default behaviour: skip\n // verify-access (handshake / introspection must work for\n // unregistered probes). With `evaluateAlwaysIfCredentialed: true`\n // AND credentials present, fall through to verify-access for the\n // audit trail; the post-verify gate-skipping branch below handles\n // the rendering side. Round-12 F9 parity (closes the round-12\n // deferral).\n const shouldEnforce = minAccessLevel !== 'none';\n if (\n minAccessLevel === 'none' &&\n (!config.evaluateAlwaysIfCredentialed || !credentials.astraId)\n ) {\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-tier-none');\n }\n return next();\n }\n\n // Round-12 (F19) + round-13 (R13-1, R13-2): symmetric purpose+action\n // resolution. Per `feedback_symmetric_fallbacks_for_symmetric_concepts.md`,\n // both concepts share the precedence chain documented at\n // `mcpToPdlss`:\n // header → _meta.astrasync.<concept> → params.arguments.<concept> → default\n //\n // Round-12 shipped purpose with `header → _meta → default`;\n // round-13 R13-1 closed the `params.arguments.purpose` fallback\n // gap. Round-13 R13-2 ships action with the same full chain in\n // one round (not staggered) to pre-empt the parallel \"I set\n // action in arguments and it didn't take\" support tickets.\n const headerPurpose = readSingleHeader(req.headers['x-astra-purpose']);\n const headerAction = readSingleHeader(req.headers['x-astra-action']);\n const pdlss = mcpToPdlss(parsed, headerPurpose, headerAction);\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 const counterpartyUrl =\n config.counterpartyUrl || `${req.protocol}://${req.get('host')}${req.path}`;\n const shouldRecordDecisions = recordDecisions !== false;\n\n const result = await verify(config, {\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 },\n });\n\n req.agentVerification = result;\n const sessionId = (result as EnhancedVerificationResult).sessionId;\n const correlationId = (result as EnhancedVerificationResult).correlationId;\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 // Round-18 G4: short-circuit on either axis failing.\n if (!result.identityVerified || !result.policyAllowed) {\n if (shouldRecordDecisions && sessionId) {\n recordDecision(config, sessionId, 'denied', result.denialReasons?.[0]).catch(() => {});\n }\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 if (!hasMinimumAccess(result.accessLevel, minAccessLevel)) {\n // Round-12 (F12): synthesise the structured failure entry so the\n // partner-facing response carries the same shape as every other\n // denial dimension. Guidance positions step-up only — increasing\n // trust score or lowering the route floor both read as gaming the\n // gate. Step-up flow ships separately this month.\n const insufficientFailure = {\n dimension: 'access_level.insufficient',\n message: `Tool requires accessLevel '${minAccessLevel}'; agent has '${result.accessLevel}'.`,\n guidance:\n \"Request elevated access via step-up verification (coming soon — ships this month). Step-up lets the agent owner approve a one-time elevation for this specific counterparty + purpose without changing the agent's baseline trust score.\",\n };\n result.failures = [...(result.failures ?? []), insufficientFailure];\n result.denialReasons = [...(result.denialReasons ?? []), insufficientFailure.message];\n\n // v2.3.9 (defect #34): server granted but local enforcement\n // (toolGate / methodGate / tier floor) is rejecting. Record this\n // as a local override so the activity feed surfaces the\n // dashboard-vs-runtime divergence as a distinct event.\n //\n // v2.3.10 (defect #34, round-4): anonymous traffic has no session\n // → fall back to `correlationId` and the sessionless local-override\n // endpoint. Authenticated traffic still uses sessionId/recordDecision.\n if (shouldRecordDecisions) {\n const overrideKind: 'toolGate' | 'methodGate' | 'other' =\n gateSource === 'toolGate'\n ? 'toolGate'\n : gateSource === 'methodGate'\n ? 'methodGate'\n : 'other';\n const override = {\n overriddenBy: overrideKind,\n ...(parsed.toolName && { toolName: parsed.toolName }),\n requestedLevel: minAccessLevel,\n grantedLevel: result.accessLevel,\n };\n if (sessionId) {\n recordDecision(config, sessionId, 'denied', result.denialReasons?.[0], override).catch(\n () => {}\n );\n } else if (correlationId) {\n recordAnonymousLocalOverride(\n config,\n correlationId,\n override,\n result.denialReasons?.[0]\n ).catch(() => {});\n }\n }\n onDenied(result, req, res);\n return;\n }\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 // Fail open by default — the underlying MCP server handler still\n // exists and can apply its own access control. A logger hook is a\n // future enhancement.\n // eslint-disable-next-line no-console\n console.error('[VerificationGateway/MCP] Middleware error:', error);\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 mcpRiskTier,\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":["result"]}
|
|
1
|
+
{"version":3,"sources":["../../src/access-levels.ts","../../src/version.ts","../../src/verify.ts","../../src/transport/mcp-server.ts","../../src/adapters/mcp.ts"],"sourcesContent":["/**\n * AstraSync Universal Verification Gateway - Access Level Definitions\n *\n * Defines the hierarchy and capabilities of each access level.\n *\n * v2.3.9 (defect #30): renamed `'guidance'` band → `'restricted'`. See\n * `types.ts` AccessLevel for the rationale (value-name collision with the\n * `guidance: {...}` help-payload object on VerificationResult).\n */\n\nimport type { AccessLevel, TrustLevel } from './types';\n\n/**\n * Access level hierarchy (higher number = more access)\n */\nexport const ACCESS_LEVEL_HIERARCHY: Record<AccessLevel, number> = {\n none: 0,\n restricted: 1,\n 'read-only': 2,\n standard: 3,\n full: 4,\n internal: 5,\n};\n\n/**\n * Access level descriptions for UI\n */\nexport const ACCESS_LEVEL_DESCRIPTIONS: Record<AccessLevel, string> = {\n none: 'No access - credentials required',\n restricted: 'Restricted access - registration prompt only',\n 'read-only': 'Read-only access - can browse but not modify',\n standard: 'Standard access - normal operations per PDLSS policy',\n full: 'Full access - all operations for high-trust agents',\n internal: 'Internal access - organization member privileges',\n};\n\n/**\n * Default trust score thresholds for access levels\n */\nexport const DEFAULT_TRUST_THRESHOLDS: Record<AccessLevel, number> = {\n none: 0,\n restricted: 0,\n 'read-only': 20,\n standard: 40,\n full: 70,\n internal: 0, // Internal is based on org membership, not score\n};\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\n/**\n * Check if access level A is greater than or equal to access level B\n */\nexport function hasMinimumAccess(actual: AccessLevel, required: AccessLevel): boolean {\n return ACCESS_LEVEL_HIERARCHY[actual] >= ACCESS_LEVEL_HIERARCHY[required];\n}\n\n/**\n * Get the highest access level for a given trust score\n */\nexport function getAccessLevelForScore(\n trustScore: number,\n thresholds: Record<AccessLevel, number> = DEFAULT_TRUST_THRESHOLDS\n): AccessLevel {\n if (trustScore >= thresholds.full) return 'full';\n if (trustScore >= thresholds.standard) return 'standard';\n if (trustScore >= thresholds['read-only']) return 'read-only';\n return 'restricted';\n}\n\n/**\n * Determine access level from verification result.\n *\n * v2.3.9 (defect #30): unverified callers now return `'none'` (was\n * `'guidance'`). Denials grant zero — never a positive band.\n */\nexport function determineAccessLevel(\n verified: boolean,\n trustScore: number,\n isOrgMember: boolean,\n customThresholds?: Partial<Record<AccessLevel, number>>\n): AccessLevel {\n if (!verified) {\n return 'none';\n }\n\n if (isOrgMember) {\n return 'internal';\n }\n\n const thresholds = {\n ...DEFAULT_TRUST_THRESHOLDS,\n ...customThresholds,\n };\n\n return getAccessLevelForScore(trustScore, thresholds);\n}\n\n/**\n * Access capabilities per level\n */\nexport interface AccessCapabilities {\n canRead: boolean;\n canWrite: boolean;\n canDelete: boolean;\n canAdmin: boolean;\n canAccessInternal: boolean;\n maxTransactionValue?: number;\n allowedPurposes?: string[];\n}\n\n/**\n * Get capabilities for an access level\n */\nexport function getCapabilities(accessLevel: AccessLevel): AccessCapabilities {\n switch (accessLevel) {\n case 'none':\n return {\n canRead: false,\n canWrite: false,\n canDelete: false,\n canAdmin: false,\n canAccessInternal: false,\n };\n case 'restricted':\n return {\n canRead: false,\n canWrite: false,\n canDelete: false,\n canAdmin: false,\n canAccessInternal: false,\n };\n case 'read-only':\n return {\n canRead: true,\n canWrite: false,\n canDelete: false,\n canAdmin: false,\n canAccessInternal: false,\n };\n case 'standard':\n return {\n canRead: true,\n canWrite: true,\n canDelete: false,\n canAdmin: false,\n canAccessInternal: false,\n };\n case 'full':\n return {\n canRead: true,\n canWrite: true,\n canDelete: true,\n canAdmin: false,\n canAccessInternal: false,\n };\n case 'internal':\n return {\n canRead: true,\n canWrite: true,\n canDelete: true,\n canAdmin: true,\n canAccessInternal: true,\n };\n default:\n return {\n canRead: false,\n canWrite: false,\n canDelete: false,\n canAdmin: false,\n canAccessInternal: false,\n };\n }\n}\n","/**\n * Round-13 (F14 closure / R13-7): 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. Round-13 manual bump\n * is fine — bumping both in the release-ceremony commit keeps them\n * lockstep.\n */\nexport const SDK_VERSION = '2.4.10';\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 type {\n GatewayConfig,\n AgentCredentials,\n VerificationRequest,\n VerificationResult,\n VerifiedAgent,\n VerifiedDeveloper,\n VerifiedOrganization,\n GuidanceInfo,\n AccessLevel,\n EnhancedVerificationResult,\n TokenGuidance,\n RuntimeChallengeResult,\n} from './types';\nimport { getTrustLevel, ACCESS_LEVEL_HIERARCHY } from './access-levels';\nimport { SDK_VERSION } from './version';\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 // v2.3.9 (defect #30): default for unconfigured callers is `'none'` (no\n // access). Pre-rename this defaulted to `'guidance'`, which combined with\n // a route gated at `'guidance'` to silently let unverified traffic\n // through (`hasMinimumAccess('guidance', 'guidance') === true`).\n defaultAccessLevel: 'none',\n // minTrustScore + minTrustScoreForFull deprecated in v2.3.0 — server decides.\n // Round-18.5 F4: cacheTtl deliberately unset. When undefined, cacheResult\n // applies the split default (60s autonomous / 300s step-up). When the\n // caller sets cacheTtl explicitly, that value is honoured uniformly.\n // 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/** One-shot guard for v2.3.0 deprecation warning. */\nlet deprecationWarningShown = false;\n\nasync function performInitCheck(apiBaseUrl: string, debug?: boolean): 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 fetch(probeUrl, { method: 'HEAD' });\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.startsWith('text/html')) {\n console.warn(\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 `Set disableInitChecks: true on GatewayConfig to silence this warning.`\n );\n } else if (debug) {\n console.log(\n `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`\n );\n }\n } catch (err) {\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 * Round-18.5 F4: pre-fix 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 */\nfunction getCacheKey(request: VerificationRequest): 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 request.counterpartyUrl || '',\n request.counterpartyType || '',\n request.isSubAgentRequest ? '1' : '0',\n request.parentAgentId || '',\n request.subAgentDepth ?? '',\n ].join('|');\n}\n\n/**\n * Check if cached result is still valid\n */\nfunction getCachedResult(request: VerificationRequest): VerificationResult | null {\n const key = getCacheKey(request);\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 * Round-18.5 F4: TTL splits by step-up status when the caller hasn't pinned\n * `configuredTtl` explicitly. Autonomous verdicts cache for 60s (faster\n * policy-update propagation, better security posture); step-up verdicts cache\n * for 300s (matches human-paced approval cycles, prevents thrashing during\n * the user-action window). When `configuredTtl` is set (truthy positive\n * number), it's honoured uniformly for back-compat with partners pinning a\n * specific TTL today.\n */\nconst DEFAULT_AUTONOMOUS_TTL_SECONDS = 60;\nconst DEFAULT_STEP_UP_TTL_SECONDS = 300;\n\nfunction cacheResult(\n request: VerificationRequest,\n result: VerificationResult,\n configuredTtl: number | undefined\n): void {\n const ttlSeconds =\n configuredTtl && configuredTtl > 0\n ? configuredTtl\n : result.requiresStepUp\n ? DEFAULT_STEP_UP_TTL_SECONDS\n : DEFAULT_AUTONOMOUS_TTL_SECONDS;\n const key = getCacheKey(request);\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 // 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 credentials.astraId = Array.isArray(astraIdHeader) ? astraIdHeader[0] : astraIdHeader;\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 const authHeader = headers['authorization'] || headers['Authorization'];\n if (authHeader) {\n const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;\n credentials.authorizationHeader = authValue;\n\n if (authValue.startsWith('Bearer ')) {\n credentials.jwt = authValue.slice(7);\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. Round-10 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 */\ntype GuidanceSource = 'no_credentials' | 'api_error';\n\n/**\n * Create guidance response for unverified agents or for API-error fallback.\n *\n * Round-10 (#47, O5): split source 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: { source?: GuidanceSource; correlationId?: string } = {}\n): VerificationResult {\n const source = options.source ?? 'no_credentials';\n const isApiError = source === 'api_error';\n\n const guidance: GuidanceInfo = isApiError\n ? {\n message:\n 'Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.',\n registrationUrl: `${config.apiBaseUrl.replace('/api', '')}/register`,\n documentationUrl: `${config.apiBaseUrl.replace('/api', '')}/docs/agent-access`,\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: `${config.apiBaseUrl.replace('/api', '')}/register`,\n documentationUrl: `${config.apiBaseUrl.replace('/api', '')}/docs/agent-access`,\n steps: [\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 };\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 // v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.\n // Adapters additionally short-circuit on `!identityVerified ||\n // !policyAllowed` before the gate check, but the access level still has\n // to be honest at the data layer so downstream consumers (SDK adapters\n // in other languages, custom integrations) inherit the correct\n // semantics.\n accessLevel: 'none',\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: isApiError\n ? [\n {\n dimension: 'verify_access.api_error',\n message: reason ?? 'Verification temporarily unavailable',\n guidance: guidance.message,\n },\n ]\n : undefined,\n correlationId: options.correlationId,\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 /**\n * Server-decided access level. Read verbatim — do NOT remap client-side.\n * The server resolves this from endpoint policy + agent trust score using\n * the canonical thresholds (see backend `apps/backend/src/utils/access-levels.ts`).\n */\n accessLevel?: AccessLevel;\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 validUntil?: string;\n proofType: 'reference' | 'zkp';\n proof: string;\n }>;\n };\n error?: string;\n /**\n * Round-10 (#47, O5): 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 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 purpose: requestData.purpose || 'general',\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\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. The backend's authenticate middleware\n // accepts either a JWT or an API key (starts with kya_) via `Authorization: Bearer <token>`.\n // Credential-supplied auth header (e.g. the agent's own token) takes priority.\n if (credentials.authorizationHeader) {\n headers['Authorization'] = credentials.authorizationHeader;\n } else if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n }\n // Legacy header kept for compatibility with any middleware that reads it directly.\n if (config.apiKey) {\n headers['X-API-Key'] = config.apiKey;\n }\n\n try {\n const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\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 access: {\n allowed: false,\n accessLevel: 'none',\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 // 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 };\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 * Main verification function\n */\nexport async function verify(\n config: GatewayConfig,\n request: VerificationRequest\n): Promise<VerificationResult> {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n\n // One-time init self-test — fire-and-forget, never blocks verify().\n if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {\n void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug);\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-level decisions ' +\n '(the SDK reads access.accessLevel from the verify-access response). 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. Round-18.5 F4: caching on unless caller explicitly\n // sets cacheTtl: 0 to disable. Undefined falls through to the split default\n // (60s autonomous / 300s step-up) at cacheResult write time.\n if (mergedConfig.cacheTtl !== 0) {\n const cached = getCachedResult(request);\n if (cached) {\n if (mergedConfig.debug) {\n console.log('[VerificationGateway] Returning cached result');\n }\n return cached;\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 // 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 return createGuidanceResponse(mergedConfig, apiResponse.error, {\n source: 'api_error',\n correlationId: (apiResponse as { correlationId?: string }).correlationId,\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 // v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.\n // Pre-rename this hardcoded `'guidance'`, which conflated with the\n // colocated `guidance: {...}` help-payload object below and let\n // denied requests pass any route gated at `'guidance'` because\n // `hasMinimumAccess('guidance', 'guidance') === true`. Adapters now\n // ALSO short-circuit on `!identityVerified || !policyAllowed` before\n // the gate check — belt-and-braces.\n accessLevel: 'none',\n denialReasons:\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: `${mergedConfig.apiBaseUrl?.replace('/api', '')}/register`,\n documentationUrl: `${mergedConfig.apiBaseUrl?.replace('/api', '')}/docs/pdlss`,\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 // v2.3.10 (defect #34, round-4): anonymous traffic has no session →\n // correlationId is the linking key for paired local_override events.\n correlationId: (apiResponse as Record<string, unknown>).correlationId 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 };\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 // Server is the single source of truth for access level. SDK reads\n // apiResponse.access.accessLevel verbatim — no client-side trust-score remap.\n // Fallback to 'standard' if the server response is missing the field (older\n // backend without the v2.3.0 contract); it covers the verified-access case.\n const accessLevel: AccessLevel = apiResponse.access?.accessLevel ?? 'standard';\n\n const result: EnhancedVerificationResult = {\n // Round-18 G4: backend allowed access. Identity is verified (we resolved\n // the caller to an agent) and policy passed all gates. Read idVerified\n // from verificationContext for symmetry with the deny branch; default true\n // on success path since `access.allowed === true` implies identity was\n // resolvable (anonymous-allow paths flow through createGuidanceResponse).\n identityVerified:\n (apiResponse.verificationContext as { idVerified?: boolean } | undefined)?.idVerified !==\n false,\n policyAllowed: true,\n accessLevel,\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 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 };\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 result.accessLevel = 'none';\n result.denialReasons = result.recommendationReasons || [\n 'Access denied by AstraSync recommendation',\n ];\n if (result.runtimeChallenge) {\n result.guidance = {\n message: `Verification failed: ${result.runtimeChallenge.reason || 'runtime challenge failed'}`,\n registrationUrl: `${mergedConfig.apiBaseUrl?.replace('/api', '')}/register`,\n documentationUrl: `${mergedConfig.apiBaseUrl?.replace('/api', '')}/docs/runtime-challenge`,\n };\n }\n } else if (result.recommendation === 'step_up_required') {\n result.requiresStepUp = true;\n if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY['read-only']) {\n result.accessLevel = 'read-only';\n }\n result.denialReasons = result.recommendationReasons || ['Step-up verification required'];\n }\n\n // Cache the result (skip caching denials — agent may fix challenge endpoint\n // and retry). Round-18.5 F4: cacheResult applies split default (60s/300s)\n // when configuredTtl is undefined; honours the caller's value when set.\n if (mergedConfig.cacheTtl !== 0 && result.recommendation !== 'deny') {\n cacheResult(request, result, mergedConfig.cacheTtl);\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 */\n/**\n * v2.3.9 (defect #34): optional override metadata. Set when the SDK's\n * local enforcement (toolGate / methodGate / trustScore floor) rejected\n * a request the SERVER had granted. Backend emits a distinct\n * `verification.local_override` event so the activity feed surfaces the\n * divergence as a separate row.\n */\nexport interface DecisionOverride {\n overriddenBy: 'toolGate' | 'methodGate' | 'trustScore' | 'other';\n toolName?: string;\n requestedLevel?: AccessLevel;\n grantedLevel?: AccessLevel;\n}\n\nexport async function recordDecision(\n config: GatewayConfig,\n sessionId: string,\n decision: 'granted' | 'denied',\n reason?: string,\n override?: DecisionOverride\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 fetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n decision,\n reason,\n ...(override && {\n overriddenBy: override.overriddenBy,\n toolName: override.toolName,\n requestedLevel: override.requestedLevel,\n grantedLevel: override.grantedLevel,\n }),\n }),\n }).catch(() => {\n /* fire-and-forget */\n });\n}\n\n/**\n * v2.3.10 (defect #34, round-4): record a SDK-side local override for an\n * anonymous verify-access response. Anonymous traffic has no session row, so\n * `recordDecision` (above) doesn't apply — but we still need to surface the\n * dashboard-vs-runtime divergence (e.g. server granted with audit warning\n * but local toolGate floor denied) on the activity feed.\n *\n * Backend ties the resulting `verification.local_override` event back to the\n * original `verification.unverified_audit` event via `correlationId`. The\n * endpoint is sessionless — see the docstring on the backend route for the\n * abuse-mitigation rationale (rate-limited per IP).\n *\n * Fire-and-forget — errors are silently swallowed.\n */\nexport async function recordAnonymousLocalOverride(\n config: GatewayConfig,\n correlationId: string,\n override: DecisionOverride,\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 fetch(`${config.apiBaseUrl}/agents/verify-access/local-override`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n correlationId,\n reason,\n overriddenBy: override.overriddenBy,\n toolName: override.toolName,\n requestedLevel: override.requestedLevel,\n grantedLevel: override.grantedLevel,\n }),\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 fetch(\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 minAccessLevel: 'none' | 'restricted' | 'read-only' | 'standard' | 'full' | 'internal';\n minTrustScore?: number;\n requiredPurposes?: string[];\n allowedPurposes?: string[];\n allowedJurisdictions?: string[];\n maxDuration?: number;\n maxTransactionValue?: number;\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): Promise<VerificationResult> {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n const result = await verify(mergedConfig, { ...request, createSession: true });\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 counterpartyUrl: string;\n counterpartyType?: string;\n sourceIp?: string;\n userAgent?: string;\n requestPath?: string;\n requestMethod?: string;\n }\n): Promise<void> {\n const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl!;\n\n await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {\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 * 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 fetch(`${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 * Round-18 G4: 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 accessLevel: AccessLevel;\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 accessLevel: result.accessLevel,\n reason: result.denialReasons?.[0],\n };\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 * - `mcpRiskTier(parsed)` — recommended `minAccessLevel` per method\n * so a single MCP middleware can split\n * `initialize` / `tools/list` (low gate)\n * from `tools/call` (high gate).\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\nimport type { AccessLevel } from '../types';\n\n/**\n * Header carrying upstream verify-access proof so an inner-hop REST endpoint\n * can dedupe (skip verify-access when the same ASTRA-id was already verified\n * a few ms earlier). Value 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 a dedupe\n * advisory. Pair with the existing X-Astra-Id auth.\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/**\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 * Round-12 (F19) + round-13 (R13-1): 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 `'mcp_invoke'`.\n */\n purposeFromBody?: string;\n /** Which body location resolved `purposeFromBody`. */\n purposeSourceFromBody?: 'meta' | 'tool_argument';\n /**\n * Round-13 (R13-2): 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 (round-13 R13-1, R13-2). 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. Doc-stable so\n * dashboards and audits can correlate consistently across cohort-3 partners.\n *\n * Rules:\n * - `purpose` is always `mcp_invoke` for MCP traffic — sets the high-level\n * PDLSS bucket.\n * - `action` is the JSON-RPC method, optionally `:tool_name` suffixed for\n * `tools/call`. Lets PDLSS allowlist specific tools.\n * - `resource` is `mcp:tool/<name>` for `tools/call`, `mcp:method/<method>`\n * otherwise. Lets PDLSS scope on tool identity.\n */\nexport interface McpPdlssMapping {\n purpose: string;\n action: string;\n resource: string;\n /**\n * Round-13 (R13-1 / R13-2): the resolution channel for purpose and\n * action — disjoint enums sharing the same structure per\n * `feedback_symmetric_fallbacks_for_symmetric_concepts.md`. Adapters\n * log both at debug level so partners can confirm header / body\n * pass-through, support can triage tickets, and we can watch the\n * `default_*` / `transport_layer` decay over time as integrations\n * mature.\n *\n * Round-12 (F19) used a narrower `purposeSource: 'header' |\n * 'tool_argument' | 'default_mcp_invoke'` — round-13 widens to also\n * carry the `_meta` source distinct from `tool_argument`, so the round-13\n * R13-1 fallback (which now reads both `_meta.astrasync.purpose` AND\n * `params.arguments.purpose`) can report WHICH body location resolved.\n */\n purposeSource: 'header' | 'meta' | 'tool_argument' | 'default_mcp_invoke';\n actionSource: 'header' | 'meta' | 'tool_argument' | 'transport_layer';\n}\n\n/**\n * Round-13 (R13-1 + R13-2) — canonical precedence chain documented ONCE,\n * applied identically to both purpose and action. Per\n * `feedback_symmetric_fallbacks_for_symmetric_concepts.md` — drift\n * between two paired concepts generates the same support-ticket class\n * repeatedly, so the resolution order is:\n *\n * 1. `X-Astra-<concept>` HTTP header (caller's explicit override)\n * 2. `params._meta.astrasync.<concept>` body field (canonical SDK location)\n * 3. `params.arguments.<concept>` body field (legacy / conventional)\n * 4. Transport-layer default:\n * purpose → 'mcp_invoke'\n * action → '<method>:<toolName>' (or just '<method>')\n *\n * The header tier is the adapter's job (it has `req.headers` access);\n * this function takes the resolved `headerPurpose` / `headerAction`\n * inputs and combines them with the body extraction already done in\n * `parseMcpJsonRpc`. The discriminator is reported on `purposeSource` /\n * `actionSource` for debug logging.\n */\nexport function mcpToPdlss(\n parsed: ParsedMcpRequest,\n headerPurpose?: string,\n headerAction?: string\n): McpPdlssMapping {\n const resource = parsed.toolName ? `mcp:tool/${parsed.toolName}` : `mcp:method/${parsed.method}`;\n\n // Purpose resolution: header > body (_meta, then tool_argument) > default\n let purpose: string;\n let purposeSource: McpPdlssMapping['purposeSource'];\n 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 = 'mcp_invoke';\n purposeSource = 'default_mcp_invoke';\n }\n\n // Action resolution: identical chain, transport-layer default differs\n let action: string;\n let actionSource: McpPdlssMapping['actionSource'];\n 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 {\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 * Recommended minimum access level per method type. The MCP middleware uses\n * this to split low-risk handshake / introspection traffic from high-risk\n * tool execution — defect (a) from the cohort-3 review.\n *\n * - `initialize` / `notifications/initialized` → `none` (handshake must work for unregistered probes)\n * - `tools/list` / `prompts/list` / `resources/list` → `none` (introspection is public-surface)\n * - `ping` → `none`\n * - `resources/read` → `read-only`\n * - `tools/call` → `standard` (default — overridable per-tool)\n * - everything else → `standard` (least-privilege fallback)\n */\nexport function mcpRiskTier(parsed: ParsedMcpRequest): AccessLevel {\n if (parsed.isInitialize || parsed.method === 'notifications/initialized') return 'none';\n if (parsed.isIntrospection) return 'none';\n if (parsed.method === 'resources/read') return 'read-only';\n if (parsed.isToolCall) return 'standard';\n return 'standard';\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**. Forwards `purpose=mcp_invoke`,\n * `action=method[:tool]`, `resource=mcp:tool/<name>` so audit traces\n * are stable across cohort-3 partners. See `transport/mcp-server.ts`\n * 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 * // Optional per-tool overrides — tools not listed get the default tier\n * // from `mcpRiskTier` (`tools/call` → 'standard').\n * toolGates: {\n * start_checkout: 'standard',\n * confirm_purchase: 'full',\n * browse_catalog: 'read-only',\n * },\n * }),\n * yourMcpServerHandler,\n * );\n * ```\n */\n\nimport type { Request, Response, NextFunction, RequestHandler } from 'express';\nimport type {\n GatewayConfig,\n AccessLevel,\n VerificationResult,\n EnhancedVerificationResult,\n} from '../types';\nimport {\n verify,\n extractCredentials,\n recordDecision,\n recordAnonymousLocalOverride,\n} from '../verify';\nimport { hasMinimumAccess } from '../access-levels';\nimport {\n parseMcpJsonRpc,\n mcpToPdlss,\n mcpRiskTier,\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 }\n }\n}\n\nexport interface McpMiddlewareOptions extends GatewayConfig {\n /**\n * Per-tool override for the minimum access level. Tools not listed inherit\n * the default tier from `mcpRiskTier` (`tools/call` → `'standard'`). Use\n * for high-risk tools that demand `'full'` or low-risk read-only tools.\n */\n toolGates?: Record<string, AccessLevel>;\n /**\n * Per-method override (e.g. tighten `tools/list` to `'read-only'` if you\n * don't want unregistered probes seeing your tool catalogue). Matches by\n * exact JSON-RPC method string.\n */\n methodGates?: Record<string, AccessLevel>;\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 `false`, don't trust an inbound `X-Astra-Verified-Hop` header (always\n * call verify-access). Default `true` — recommended for inner-hop endpoints\n * called by your own MCP tools.\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\n/**\n * Default MCP denied handler.\n *\n * Round-10 (#47, O5): 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 * (#49/O10) so the header reliably distinguishes gate-evaluated denials\n * from gate-skipped pass-throughs.\n */\n/**\n * Round-13: 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 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 // 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 code: !result.identityVerified ? -32000 : -32001,\n message: result.denialReasons?.[0] ?? 'Access denied',\n data: {\n accessLevel: result.accessLevel,\n guidance: result.guidance,\n // Round-10: aggregated per-dimension detail + correlation handle.\n failures: result.failures,\n correlationId: result.correlationId,\n },\n },\n });\n}\n\n/**\n * Resolve the effective minimum access level for a parsed MCP request.\n * Order: per-tool override → per-method override → tier-default from\n * `mcpRiskTier`.\n *\n * v2.3.9 (defect #34) returns the SOURCE of the resolution alongside the\n * level so the gate-failure site can attribute a local override correctly\n * (`toolGate` / `methodGate` / `tier`) when reporting back to the\n * activity feed.\n */\nfunction resolveMinAccessLevel(\n parsed: ParsedMcpRequest,\n opts: { toolGates?: Record<string, AccessLevel>; methodGates?: Record<string, AccessLevel> }\n): { level: AccessLevel; source: 'toolGate' | 'methodGate' | 'tier' } {\n if (parsed.toolName && opts.toolGates && opts.toolGates[parsed.toolName] !== undefined) {\n return { level: opts.toolGates[parsed.toolName], source: 'toolGate' };\n }\n if (opts.methodGates && opts.methodGates[parsed.method] !== undefined) {\n return { level: opts.methodGates[parsed.method], source: 'methodGate' };\n }\n return { level: mcpRiskTier(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 = true,\n verifiedHopMaxAgeMs,\n recordDecisions,\n enableRuntimeChallenge = true,\n ...config\n } = options;\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 { level: minAccessLevel, source: gateSource } = resolveMinAccessLevel(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 // Risk tier `none` short-circuit. Default behaviour: skip\n // verify-access (handshake / introspection must work for\n // unregistered probes). With `evaluateAlwaysIfCredentialed: true`\n // AND credentials present, fall through to verify-access for the\n // audit trail; the post-verify gate-skipping branch below handles\n // the rendering side. Round-12 F9 parity (closes the round-12\n // deferral).\n const shouldEnforce = minAccessLevel !== 'none';\n if (\n minAccessLevel === 'none' &&\n (!config.evaluateAlwaysIfCredentialed || !credentials.astraId)\n ) {\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-tier-none');\n }\n return next();\n }\n\n // Round-12 (F19) + round-13 (R13-1, R13-2): symmetric purpose+action\n // resolution. Per `feedback_symmetric_fallbacks_for_symmetric_concepts.md`,\n // both concepts share the precedence chain documented at\n // `mcpToPdlss`:\n // header → _meta.astrasync.<concept> → params.arguments.<concept> → default\n //\n // Round-12 shipped purpose with `header → _meta → default`;\n // round-13 R13-1 closed the `params.arguments.purpose` fallback\n // gap. Round-13 R13-2 ships action with the same full chain in\n // one round (not staggered) to pre-empt the parallel \"I set\n // action in arguments and it didn't take\" support tickets.\n const headerPurpose = readSingleHeader(req.headers['x-astra-purpose']);\n const headerAction = readSingleHeader(req.headers['x-astra-action']);\n const pdlss = mcpToPdlss(parsed, headerPurpose, headerAction);\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 const counterpartyUrl =\n config.counterpartyUrl || `${req.protocol}://${req.get('host')}${req.path}`;\n const shouldRecordDecisions = recordDecisions !== false;\n\n const result = await verify(config, {\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 },\n });\n\n req.agentVerification = result;\n const sessionId = (result as EnhancedVerificationResult).sessionId;\n const correlationId = (result as EnhancedVerificationResult).correlationId;\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 // Round-18 G4: short-circuit on either axis failing.\n if (!result.identityVerified || !result.policyAllowed) {\n if (shouldRecordDecisions && sessionId) {\n recordDecision(config, sessionId, 'denied', result.denialReasons?.[0]).catch(() => {});\n }\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 if (!hasMinimumAccess(result.accessLevel, minAccessLevel)) {\n // Round-12 (F12): synthesise the structured failure entry so the\n // partner-facing response carries the same shape as every other\n // denial dimension. Guidance positions step-up only — increasing\n // trust score or lowering the route floor both read as gaming the\n // gate. Step-up flow ships separately this month.\n const insufficientFailure = {\n dimension: 'access_level.insufficient',\n message: `Tool requires accessLevel '${minAccessLevel}'; agent has '${result.accessLevel}'.`,\n guidance:\n \"Request elevated access via step-up verification (coming soon — ships this month). Step-up lets the agent owner approve a one-time elevation for this specific counterparty + purpose without changing the agent's baseline trust score.\",\n };\n result.failures = [...(result.failures ?? []), insufficientFailure];\n result.denialReasons = [...(result.denialReasons ?? []), insufficientFailure.message];\n\n // v2.3.9 (defect #34): server granted but local enforcement\n // (toolGate / methodGate / tier floor) is rejecting. Record this\n // as a local override so the activity feed surfaces the\n // dashboard-vs-runtime divergence as a distinct event.\n //\n // v2.3.10 (defect #34, round-4): anonymous traffic has no session\n // → fall back to `correlationId` and the sessionless local-override\n // endpoint. Authenticated traffic still uses sessionId/recordDecision.\n if (shouldRecordDecisions) {\n const overrideKind: 'toolGate' | 'methodGate' | 'other' =\n gateSource === 'toolGate'\n ? 'toolGate'\n : gateSource === 'methodGate'\n ? 'methodGate'\n : 'other';\n const override = {\n overriddenBy: overrideKind,\n ...(parsed.toolName && { toolName: parsed.toolName }),\n requestedLevel: minAccessLevel,\n grantedLevel: result.accessLevel,\n };\n if (sessionId) {\n recordDecision(config, sessionId, 'denied', result.denialReasons?.[0], override).catch(\n () => {}\n );\n } else if (correlationId) {\n recordAnonymousLocalOverride(\n config,\n correlationId,\n override,\n result.denialReasons?.[0]\n ).catch(() => {});\n }\n }\n onDenied(result, req, res);\n return;\n }\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 // Fail open by default — the underlying MCP server handler still\n // exists and can apply its own access control. A logger hook is a\n // future enhancement.\n // eslint-disable-next-line no-console\n console.error('[VerificationGateway/MCP] Middleware error:', error);\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 mcpRiskTier,\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":["result"]}
|