@astrasyncai/verification-gateway 3.12.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +106 -29
  2. package/dist/adapter-interface/interface.d.mts +2 -2
  3. package/dist/adapter-interface/interface.d.ts +2 -2
  4. package/dist/adapters/express.d.mts +2 -2
  5. package/dist/adapters/express.d.ts +2 -2
  6. package/dist/adapters/express.js +482 -22
  7. package/dist/adapters/express.js.map +1 -1
  8. package/dist/adapters/express.mjs +482 -22
  9. package/dist/adapters/express.mjs.map +1 -1
  10. package/dist/adapters/http-pdlss.d.mts +3 -3
  11. package/dist/adapters/http-pdlss.d.ts +3 -3
  12. package/dist/adapters/http-pdlss.js.map +1 -1
  13. package/dist/adapters/http-pdlss.mjs.map +1 -1
  14. package/dist/adapters/mcp.d.mts +56 -36
  15. package/dist/adapters/mcp.d.ts +56 -36
  16. package/dist/adapters/mcp.js +309 -10
  17. package/dist/adapters/mcp.js.map +1 -1
  18. package/dist/adapters/mcp.mjs +309 -10
  19. package/dist/adapters/mcp.mjs.map +1 -1
  20. package/dist/adapters/nextjs.d.mts +2 -2
  21. package/dist/adapters/nextjs.d.ts +2 -2
  22. package/dist/adapters/nextjs.js +518 -25
  23. package/dist/adapters/nextjs.js.map +1 -1
  24. package/dist/adapters/nextjs.mjs +518 -25
  25. package/dist/adapters/nextjs.mjs.map +1 -1
  26. package/dist/adapters/sdk.d.mts +2 -2
  27. package/dist/adapters/sdk.d.ts +2 -2
  28. package/dist/adapters/sdk.js +72 -9
  29. package/dist/adapters/sdk.js.map +1 -1
  30. package/dist/adapters/sdk.mjs +72 -9
  31. package/dist/adapters/sdk.mjs.map +1 -1
  32. package/dist/agent/index.d.mts +2 -2
  33. package/dist/agent/index.d.ts +2 -2
  34. package/dist/agent/index.js.map +1 -1
  35. package/dist/agent/index.mjs.map +1 -1
  36. package/dist/bin/astrasync-claude-hook.js +83 -20
  37. package/dist/bin/astrasync-codex-hook.js +83 -20
  38. package/dist/bin/astrasync-guard.js +83 -20
  39. package/dist/bin/astrasync.js +150 -33
  40. package/dist/browser/background.js +83 -20
  41. package/dist/browser/background.js.map +1 -1
  42. package/dist/browser/background.mjs +83 -20
  43. package/dist/browser/background.mjs.map +1 -1
  44. package/dist/browser/browser-adapter.d.mts +2 -2
  45. package/dist/browser/browser-adapter.d.ts +2 -2
  46. package/dist/claude-code/claude-code-adapter.d.mts +2 -2
  47. package/dist/claude-code/claude-code-adapter.d.ts +2 -2
  48. package/dist/cli/index.d.mts +2 -2
  49. package/dist/cli/index.d.ts +2 -2
  50. package/dist/codex/index.d.mts +3 -3
  51. package/dist/codex/index.d.ts +3 -3
  52. package/dist/codex/index.js +83 -20
  53. package/dist/codex/index.js.map +1 -1
  54. package/dist/codex/index.mjs +83 -20
  55. package/dist/codex/index.mjs.map +1 -1
  56. package/dist/cursor/cursor-adapter.d.mts +2 -2
  57. package/dist/cursor/cursor-adapter.d.ts +2 -2
  58. package/dist/cursor/extension.d.mts +2 -2
  59. package/dist/cursor/extension.d.ts +2 -2
  60. package/dist/cursor/extension.js +83 -20
  61. package/dist/cursor/extension.js.map +1 -1
  62. package/dist/cursor/extension.mjs +83 -20
  63. package/dist/cursor/extension.mjs.map +1 -1
  64. package/dist/edge-config.d.mts +22 -7
  65. package/dist/edge-config.d.ts +22 -7
  66. package/dist/edge-config.js.map +1 -1
  67. package/dist/edge-config.mjs.map +1 -1
  68. package/dist/edge-core/index.d.mts +425 -0
  69. package/dist/edge-core/index.d.ts +425 -0
  70. package/dist/edge-core/index.js +1482 -0
  71. package/dist/edge-core/index.js.map +1 -0
  72. package/dist/edge-core/index.mjs +1437 -0
  73. package/dist/edge-core/index.mjs.map +1 -0
  74. package/dist/{express-B39o89gf.d.mts → express-BVd1_3FE.d.ts} +9 -7
  75. package/dist/{express-BuxKNO_q.d.ts → express-D_4hTn5Z.d.mts} +9 -7
  76. package/dist/gateway/gateway.d.mts +2 -2
  77. package/dist/gateway/gateway.d.ts +2 -2
  78. package/dist/gateway/gateway.js +83 -20
  79. package/dist/gateway/gateway.js.map +1 -1
  80. package/dist/gateway/gateway.mjs +83 -20
  81. package/dist/gateway/gateway.mjs.map +1 -1
  82. package/dist/git-trigger/git-hooks.d.mts +2 -2
  83. package/dist/git-trigger/git-hooks.d.ts +2 -2
  84. package/dist/{index-CLHdqs2M.d.ts → index-B0YHu_SP.d.ts} +30 -27
  85. package/dist/{index-DME3w3lf.d.mts → index-BPEBlOsE.d.mts} +30 -27
  86. package/dist/{index-CqydB4ks.d.mts → index-DQb5-_1X.d.mts} +1 -1
  87. package/dist/{index-BG62SXso.d.ts → index-T1aBoUcc.d.ts} +1 -1
  88. package/dist/index.d.mts +11 -11
  89. package/dist/index.d.ts +11 -11
  90. package/dist/index.js +595 -272
  91. package/dist/index.js.map +1 -1
  92. package/dist/index.mjs +592 -272
  93. package/dist/index.mjs.map +1 -1
  94. package/dist/local-evaluator/evaluator.d.mts +2 -2
  95. package/dist/local-evaluator/evaluator.d.ts +2 -2
  96. package/dist/metadata-capture.d.mts +63 -4
  97. package/dist/metadata-capture.d.ts +63 -4
  98. package/dist/metadata-capture.js +67 -0
  99. package/dist/metadata-capture.js.map +1 -1
  100. package/dist/metadata-capture.mjs +64 -0
  101. package/dist/metadata-capture.mjs.map +1 -1
  102. package/dist/{nextjs-D74W7wt-.d.mts → nextjs-WyeVr2Kp.d.mts} +3 -3
  103. package/dist/{nextjs-Ddlw_J1J.d.ts → nextjs-w7RTbP1P.d.ts} +3 -3
  104. package/dist/platform-signatures.d.mts +5 -6
  105. package/dist/platform-signatures.d.ts +5 -6
  106. package/dist/platform-signatures.js.map +1 -1
  107. package/dist/platform-signatures.mjs.map +1 -1
  108. package/dist/registration/index.d.mts +10 -11
  109. package/dist/registration/index.d.ts +10 -11
  110. package/dist/registration/index.js.map +1 -1
  111. package/dist/registration/index.mjs.map +1 -1
  112. package/dist/{sdk-CT7gdkfR.d.ts → sdk-CbTNIkAa.d.mts} +4 -4
  113. package/dist/{sdk-Bn6bj7kt.d.mts → sdk-QeX0Z8Ho.d.ts} +4 -4
  114. package/dist/transport/index.d.mts +2 -2
  115. package/dist/transport/index.d.ts +2 -2
  116. package/dist/transport/index.js.map +1 -1
  117. package/dist/transport/index.mjs.map +1 -1
  118. package/dist/{types-DHu7m9HI.d.ts → types-BLUx92FJ.d.ts} +1 -1
  119. package/dist/{types-Djw4GLtz.d.mts → types-DqfPU5Bl.d.mts} +1 -1
  120. package/dist/{types-BntuzEEn.d.mts → types-r850cOt0.d.mts} +121 -88
  121. package/dist/{types-SDu4Llbe.d.ts → types-sMxBT-nM.d.ts} +121 -88
  122. package/dist/ui/index.d.mts +11 -7
  123. package/dist/ui/index.d.ts +11 -7
  124. package/dist/ui/index.js +13 -9
  125. package/dist/ui/index.js.map +1 -1
  126. package/dist/ui/index.mjs +10 -8
  127. package/dist/ui/index.mjs.map +1 -1
  128. package/dist/verify.d.mts +11 -8
  129. package/dist/verify.d.ts +11 -8
  130. package/dist/verify.js +76 -8
  131. package/dist/verify.js.map +1 -1
  132. package/dist/verify.mjs +75 -8
  133. package/dist/verify.mjs.map +1 -1
  134. package/package.json +8 -2
@@ -0,0 +1,1437 @@
1
+ // src/edge-config.ts
2
+ var DEFAULT_EDGE_CONFIG = Object.freeze({
3
+ version: 1,
4
+ mode: "observe",
5
+ depth: "classify",
6
+ pathRules: [],
7
+ sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
8
+ failurePosture: "open"
9
+ });
10
+ var DEFAULT_TTL_MS = 60 * 1e3;
11
+ var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
12
+ function matchEdgePathRule(rules, path) {
13
+ for (const rule of rules) {
14
+ if (globToRegExp(rule.pattern).test(path)) return rule;
15
+ }
16
+ return void 0;
17
+ }
18
+ function globToRegExp(pattern) {
19
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
20
+ return new RegExp(`^${escaped}$`, "i");
21
+ }
22
+
23
+ // src/metadata-capture.ts
24
+ var CAPTURE_SCHEMA_VERSION = 1;
25
+ var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
26
+ "cookie",
27
+ "set-cookie",
28
+ "x-csrf-token",
29
+ "proxy-authorization",
30
+ "x-astrasync-signature",
31
+ "x-astrasync-secret",
32
+ "x-hub-signature",
33
+ "x-hub-signature-256"
34
+ ]);
35
+ var SECRET_HEADER_PREFIXES = ["x-astrasync-", "kya-", "kya_"];
36
+ var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set([
37
+ "authorization",
38
+ "x-api-key",
39
+ "x-goog-api-key",
40
+ "api-key"
41
+ ]);
42
+ function isSignatureHeader(name) {
43
+ return name === "signature" || name.endsWith("-signature") || name.endsWith("-signature-256");
44
+ }
45
+ var PLATFORM_HEADER_NAMES = /* @__PURE__ */ new Set([
46
+ "openai-organization",
47
+ "openai-project",
48
+ "openai-version",
49
+ "openai-processing-ms",
50
+ "anthropic-version",
51
+ "anthropic-beta",
52
+ "x-goog-user-project",
53
+ "x-goog-api-client",
54
+ "user-agent",
55
+ "x-agent-card-url",
56
+ "x-agent-provider"
57
+ ]);
58
+ var PLATFORM_HEADER_PREFIXES = ["x-goog-", "x-stainless-", "x-anthropic-", "x-openai-"];
59
+ var KEY_FORMAT_PREFIXES = [
60
+ "sk-ant-api03",
61
+ "sk-ant-api",
62
+ "sk-ant",
63
+ "sk-proj",
64
+ "sk-svcacct",
65
+ "sk-or-v1",
66
+ "sk-or",
67
+ "sk-lf",
68
+ "sk-",
69
+ "gsk_",
70
+ "aiza",
71
+ "ya29",
72
+ "ghp_",
73
+ "gho_",
74
+ "github_pat_",
75
+ "xai-",
76
+ "pplx-",
77
+ "r8_",
78
+ "hf_",
79
+ "astra-",
80
+ "astrae-"
81
+ ];
82
+ var MAX_HEADERS = 64;
83
+ var MAX_HEADER_VALUE_BYTES = 1024;
84
+ var MAX_TOTAL_BYTES = 16 * 1024;
85
+ function byteLength(s) {
86
+ let bytes = 0;
87
+ for (let i = 0; i < s.length; i++) {
88
+ const code = s.charCodeAt(i);
89
+ if (code < 128) bytes += 1;
90
+ else if (code < 2048) bytes += 2;
91
+ else if (code >= 55296 && code <= 56319) {
92
+ bytes += 4;
93
+ i++;
94
+ } else bytes += 3;
95
+ }
96
+ return bytes;
97
+ }
98
+ function extractApiKeyFormat(rawValue) {
99
+ if (!rawValue) return void 0;
100
+ let value = rawValue.trim();
101
+ const spaceIdx = value.indexOf(" ");
102
+ if (spaceIdx > 0) {
103
+ const scheme = value.slice(0, spaceIdx).toLowerCase();
104
+ if (scheme === "bearer" || scheme === "basic" || scheme === "token") {
105
+ value = value.slice(spaceIdx + 1).trim();
106
+ }
107
+ }
108
+ const lower = value.toLowerCase();
109
+ for (const prefix of KEY_FORMAT_PREFIXES) {
110
+ if (lower.startsWith(prefix)) {
111
+ return prefix;
112
+ }
113
+ }
114
+ return void 0;
115
+ }
116
+ function isSecretHeader(name) {
117
+ if (SECRET_HEADER_NAMES.has(name)) return true;
118
+ if (SECRET_HEADER_PREFIXES.some((p) => name.startsWith(p))) return true;
119
+ if (isSignatureHeader(name)) return true;
120
+ return false;
121
+ }
122
+ function isPlatformHeader(name) {
123
+ if (PLATFORM_HEADER_NAMES.has(name)) return true;
124
+ return PLATFORM_HEADER_PREFIXES.some((p) => name.startsWith(p));
125
+ }
126
+ function sanitizeHeaders(raw) {
127
+ const headers = {};
128
+ const platformHeaders = {};
129
+ let apiKeyFormat;
130
+ let totalBytes = 0;
131
+ let count = 0;
132
+ if (!raw) return { headers };
133
+ for (const rawName of Object.keys(raw)) {
134
+ if (count >= MAX_HEADERS) break;
135
+ const name = rawName.toLowerCase();
136
+ const rawValue = raw[rawName];
137
+ if (rawValue === void 0) continue;
138
+ const joined = Array.isArray(rawValue) ? rawValue.join(", ") : String(rawValue);
139
+ if (CREDENTIAL_HEADER_NAMES.has(name)) {
140
+ const fmt = extractApiKeyFormat(joined);
141
+ if (fmt && !apiKeyFormat) apiKeyFormat = fmt;
142
+ continue;
143
+ }
144
+ if (isSecretHeader(name)) continue;
145
+ let value = joined;
146
+ if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {
147
+ value = value.slice(0, MAX_HEADER_VALUE_BYTES);
148
+ }
149
+ const entryBytes = byteLength(name) + byteLength(value);
150
+ if (totalBytes + entryBytes > MAX_TOTAL_BYTES) continue;
151
+ totalBytes += entryBytes;
152
+ count++;
153
+ headers[name] = value;
154
+ if (isPlatformHeader(name)) platformHeaders[name] = value;
155
+ }
156
+ const result = { headers };
157
+ if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;
158
+ if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
159
+ return result;
160
+ }
161
+
162
+ // src/platform-signatures.ts
163
+ var PLATFORM_AGENT_SIGNATURES = [
164
+ {
165
+ vendor: "claude",
166
+ displayName: "Anthropic Claude",
167
+ uaPatterns: [/claude/i, /anthropic/i],
168
+ agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
169
+ apiKeyFormatPatterns: [/^sk-ant/i],
170
+ headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
171
+ },
172
+ {
173
+ vendor: "chatgpt",
174
+ displayName: "OpenAI ChatGPT",
175
+ uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
176
+ agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
177
+ apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
178
+ headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
179
+ },
180
+ {
181
+ vendor: "gemini",
182
+ displayName: "Google Gemini",
183
+ uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
184
+ agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
185
+ apiKeyFormatPatterns: [/^aiza/i],
186
+ headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
187
+ },
188
+ {
189
+ vendor: "cursor",
190
+ displayName: "Cursor",
191
+ uaPatterns: [/cursor/i],
192
+ agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
193
+ },
194
+ {
195
+ vendor: "goose",
196
+ displayName: "Block Goose",
197
+ uaPatterns: [/goose/i, /block-goose/i],
198
+ agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
199
+ },
200
+ {
201
+ vendor: "perplexity",
202
+ displayName: "Perplexity",
203
+ uaPatterns: [/perplexity/i],
204
+ agentCardPatterns: [/perplexity\.ai/i],
205
+ apiKeyFormatPatterns: [/^pplx-/i]
206
+ }
207
+ ];
208
+ function matchPlatformSignature(signatures, input) {
209
+ const ua = input.userAgent ?? "";
210
+ const cardUrl = input.agentCardUrl ?? "";
211
+ const apiKeyFormat = input.apiKeyFormat ?? "";
212
+ const headerLines = [];
213
+ if (input.platformHeaders) {
214
+ for (const [name, value] of Object.entries(input.platformHeaders)) {
215
+ headerLines.push(name, `${name}: ${value}`);
216
+ }
217
+ }
218
+ for (const sig of signatures) {
219
+ const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
220
+ const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
221
+ const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
222
+ const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
223
+ if (uaMatch || cardMatch || keyMatch || headerMatch) {
224
+ const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
225
+ const cardHost = (() => {
226
+ try {
227
+ return cardUrl ? new URL(cardUrl).host : "";
228
+ } catch {
229
+ return "";
230
+ }
231
+ })();
232
+ const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
233
+ return {
234
+ vendor: sig.vendor,
235
+ fingerprintKey: `${sig.vendor}:${keyBasis}`,
236
+ displayName: sig.displayName
237
+ };
238
+ }
239
+ }
240
+ return void 0;
241
+ }
242
+ function detectPlatformFingerprint(input) {
243
+ return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
244
+ }
245
+
246
+ // src/access-levels.ts
247
+ function getTrustLevel(score) {
248
+ if (score >= 80) return "PLATINUM";
249
+ if (score >= 60) return "GOLD";
250
+ if (score >= 40) return "SILVER";
251
+ return "BRONZE";
252
+ }
253
+
254
+ // src/version.ts
255
+ var SDK_VERSION = "4.1.0";
256
+
257
+ // src/well-known.ts
258
+ var CACHE_TTL_MS = 60 * 60 * 1e3;
259
+ var cache = /* @__PURE__ */ new Map();
260
+ function getCachedWellKnownUrls(apiBaseUrl) {
261
+ return cache.get(apiBaseUrl)?.data;
262
+ }
263
+
264
+ // src/verify.ts
265
+ var DEFAULT_CONFIG = {
266
+ apiBaseUrl: "https://astrasync.ai/api",
267
+ // v2.3.9 (defect #30): default for unconfigured callers is `'none'` (no
268
+ // access). Pre-rename this defaulted to `'guidance'`, which combined with
269
+ // a route gated at `'guidance'` to silently let unverified traffic
270
+ // through (`hasMinimumAccess('guidance', 'guidance') === true`).
271
+ defaultAccessLevel: "none",
272
+ // minTrustScore + minTrustScoreForFull deprecated in v2.3.0 — server decides.
273
+ // cacheTtl deliberately unset. When undefined, cacheResult applies the 60s
274
+ // default; the caller's explicit value is honoured for cacheable results.
275
+ // Denials and step-up escalations are NEVER cached (3.6.0) regardless of
276
+ // cacheTtl. Set cacheTtl: 0 to disable caching entirely.
277
+ debug: false
278
+ };
279
+ var initCheckPerformed = false;
280
+ var deprecationWarningShown = false;
281
+ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
282
+ initCheckPerformed = true;
283
+ try {
284
+ const probeUrl = `${apiBaseUrl}/agents/verify-access`;
285
+ const response = await fetch(probeUrl, { method: "HEAD" });
286
+ const contentType = response.headers.get("content-type") ?? "";
287
+ if (contentType.startsWith("text/html")) {
288
+ const message = `[VerificationGateway] apiBaseUrl '${apiBaseUrl}' returned HTML (content-type: ${contentType}). This usually means apiBaseUrl is pointing at a marketing site instead of the API. Expected: 'https://astrasync.ai/api' (prod) or 'https://staging.astrasync.ai/api' (staging).`;
289
+ if (strictInit) {
290
+ throw new Error(`${message} (strictInit=true)`);
291
+ }
292
+ console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);
293
+ } else if (debug) {
294
+ console.log(
295
+ `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
296
+ );
297
+ }
298
+ if (apiKey) {
299
+ const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
300
+ headers: { Authorization: `Bearer ${apiKey}` }
301
+ });
302
+ if (selfCheck.status === 401) {
303
+ const body = await selfCheck.json().catch(() => ({}));
304
+ const message = `[VerificationGateway] the configured AstraSync API key was REJECTED at startup (${body.code ?? "api_key_invalid"}: ${body.message ?? "invalid key"}). Every inbound agent will be turned away until the key is rotated` + (body.remediation?.manageUrl ? ` at ${body.remediation.manageUrl}` : "") + `.`;
305
+ if (strictInit) {
306
+ throw new Error(`${message} (strictInit=true)`);
307
+ }
308
+ console.error(message);
309
+ } else if (selfCheck.ok) {
310
+ const body = await selfCheck.json().catch(() => ({}));
311
+ if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
312
+ console.warn(
313
+ `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
314
+ );
315
+ } else if (debug) {
316
+ console.log("[VerificationGateway] API key self-check passed");
317
+ }
318
+ }
319
+ }
320
+ } catch (err) {
321
+ if (strictInit) {
322
+ throw err;
323
+ }
324
+ if (debug) {
325
+ console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);
326
+ }
327
+ }
328
+ }
329
+ var verificationCache = /* @__PURE__ */ new Map();
330
+ function getCacheKey(request, counterpartyId) {
331
+ const c = request.credentials;
332
+ return [
333
+ c.astraId || "",
334
+ c.apiKey || "",
335
+ c.jwt || "",
336
+ request.purpose || "",
337
+ request.action || "",
338
+ request.resourceType || "",
339
+ request.resource || "",
340
+ request.jurisdiction || "",
341
+ request.transactionValue ?? "",
342
+ request.currency || "",
343
+ // SECURITY (cross-merchant cache leak): the merchant identity is sent via
344
+ // `config.counterpartyId`, NOT on the request, so it was previously absent
345
+ // from the key — two verifies for the SAME agent/purpose/action/value but
346
+ // DIFFERENT merchants collided, and a grant at a permissive merchant (low
347
+ // trust floor) was served for a stricter one. Same bug class as the
348
+ // duration omission (F-A1-07). counterpartyId affects the backend verdict
349
+ // (trust floor / per-route policy), so it MUST key the cache.
350
+ counterpartyId || "",
351
+ request.counterpartyUrl || "",
352
+ request.counterpartyType || "",
353
+ request.isSubAgentRequest ? "1" : "0",
354
+ request.parentAgentId || "",
355
+ request.subAgentDepth ?? "",
356
+ // Audit F-A1-07: previously-missing dimensions that DO affect the
357
+ // backend verdict. Without these, two requests with different
358
+ // durations (e.g. 60s vs 86400s) collided on the same cache key and
359
+ // the shorter-duration allow served the longer-duration request.
360
+ request.durationRequired ?? "",
361
+ request.invocationProtocol || "",
362
+ request.enableRuntimeChallenge ? "1" : "0",
363
+ // callerMetadata fields contribute to risk model; include the ones
364
+ // backend reads. sourceIp/userAgent/forwardedFor change per-request
365
+ // so their inclusion effectively forces a re-check for any varying
366
+ // client (the right behavior — IP-driven anomaly scoring shouldn't
367
+ // be cached across IPs).
368
+ request.callerMetadata?.sourceIp || "",
369
+ request.callerMetadata?.userAgent || "",
370
+ request.callerMetadata?.forwardedFor || "",
371
+ request.callerMetadata?.agentCardUrl || ""
372
+ ].join("|");
373
+ }
374
+ function getCachedResult(request, counterpartyId) {
375
+ const key = getCacheKey(request, counterpartyId);
376
+ const cached = verificationCache.get(key);
377
+ if (cached && cached.expiresAt > Date.now()) {
378
+ return cached.result;
379
+ }
380
+ if (cached) {
381
+ verificationCache.delete(key);
382
+ }
383
+ return null;
384
+ }
385
+ var DEFAULT_CACHE_TTL_SECONDS = 60;
386
+ function cacheResult(request, result, configuredTtl, counterpartyId) {
387
+ const ttlSeconds = configuredTtl && configuredTtl > 0 ? configuredTtl : DEFAULT_CACHE_TTL_SECONDS;
388
+ const key = getCacheKey(request, counterpartyId);
389
+ verificationCache.set(key, {
390
+ result,
391
+ expiresAt: Date.now() + ttlSeconds * 1e3
392
+ });
393
+ }
394
+ function extractCredentials(headers, query) {
395
+ const credentials = {};
396
+ const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
397
+ const astraIdHeader = headers["x-astra-id"] || headers["X-Astra-Id"] || headers["X-ASTRA-ID"] || headers["x-astra-agentid"] || headers["X-Astra-AgentId"] || headers["x-astra-agent-id"] || headers["X-Astra-Agent-Id"] || headers["X-ASTRA-AGENT-ID"];
398
+ if (astraIdHeader) {
399
+ const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
400
+ if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
401
+ credentials.astraId = raw;
402
+ }
403
+ }
404
+ const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
405
+ if (apiKeyHeader) {
406
+ credentials.apiKey = Array.isArray(apiKeyHeader) ? apiKeyHeader[0] : apiKeyHeader;
407
+ }
408
+ const authHeader = headers["authorization"] || headers["Authorization"];
409
+ if (authHeader) {
410
+ const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
411
+ if (typeof authValue === "string") {
412
+ credentials.authorizationHeader = authValue;
413
+ if (authValue.startsWith("Bearer ")) {
414
+ credentials.jwt = authValue.slice(7);
415
+ }
416
+ }
417
+ }
418
+ if (query) {
419
+ if (query.astraId && !credentials.astraId) {
420
+ credentials.astraId = query.astraId;
421
+ }
422
+ if (query.apiKey && !credentials.apiKey) {
423
+ credentials.apiKey = query.apiKey;
424
+ }
425
+ }
426
+ return credentials;
427
+ }
428
+ function createGuidanceResponse(_config, reason, options = {}) {
429
+ const source = options.source ?? "no_credentials";
430
+ const isApiError = source === "api_error";
431
+ const isMisconfig = source === "merchant_misconfig";
432
+ const urls = options.urls;
433
+ const guidance = isMisconfig ? {
434
+ message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or rotated). Your identity and the AstraSync platform are fine \u2014 contact the site operator. Retrying will not help.",
435
+ registrationUrl: urls?.registrationUrl ?? "",
436
+ documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
437
+ steps: [
438
+ "Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
439
+ "Do not retry: the failure is deterministic until the operator updates their key"
440
+ ]
441
+ } : isApiError ? {
442
+ message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
443
+ registrationUrl: urls?.registrationUrl ?? "",
444
+ documentationUrl: urls?.documentationUrl ?? "",
445
+ steps: [
446
+ "Retry the request with exponential backoff",
447
+ "If failures persist, share the correlationId with support"
448
+ ]
449
+ } : {
450
+ message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
451
+ registrationUrl: urls?.registrationUrl ?? "",
452
+ documentationUrl: urls?.documentationUrl ?? "",
453
+ steps: [
454
+ "Register for an AstraSync account",
455
+ "Create and register your agent",
456
+ "Add your ASTRA-ID to request headers",
457
+ "Retry your request"
458
+ ]
459
+ };
460
+ return {
461
+ // Round-18 G4: createGuidanceResponse fires for unverified-agent path or
462
+ // API-error fallback. Identity is not verified (no agent resolved);
463
+ // policy is not evaluated (we never reached the gate).
464
+ identityVerified: false,
465
+ policyAllowed: false,
466
+ // v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.
467
+ // Adapters additionally short-circuit on `!identityVerified ||
468
+ // !policyAllowed` before the gate check, but the access level still has
469
+ // to be honest at the data layer so downstream consumers (SDK adapters
470
+ // in other languages, custom integrations) inherit the correct
471
+ // semantics.
472
+ accessLevel: "none",
473
+ guidance,
474
+ denialReasons: reason ? [reason] : ["No valid agent credentials provided"],
475
+ // Round-10 (#47, O5): on API-error fallback, surface a typed failure so
476
+ // partners (and their custom onDenied handlers) can branch on
477
+ // dimension. Without this, the synthesised stub was indistinguishable
478
+ // from a real policy deny.
479
+ failures: isMisconfig ? [
480
+ {
481
+ dimension: "verify_access.merchant_misconfigured",
482
+ message: reason ?? "The integration API key was rejected by AstraSync",
483
+ guidance: guidance.message
484
+ }
485
+ ] : isApiError ? [
486
+ {
487
+ dimension: "verify_access.api_error",
488
+ message: reason ?? "Verification temporarily unavailable",
489
+ guidance: guidance.message
490
+ }
491
+ ] : void 0,
492
+ // 4.0.0: typed marker so adapters render the misconfig envelope (503 +
493
+ // MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.
494
+ ...isMisconfig && { misconfigured: true },
495
+ correlationId: options.correlationId,
496
+ attemptId: options.attemptId,
497
+ verifiedAt: /* @__PURE__ */ new Date()
498
+ };
499
+ }
500
+ async function callVerifyAccessAPI(config, request) {
501
+ const { credentials, ...requestData } = request;
502
+ const body = {
503
+ ...credentials.astraId && { agentId: credentials.astraId },
504
+ ...requestData.purpose && { purpose: requestData.purpose }
505
+ };
506
+ if (requestData.action) body.action = requestData.action;
507
+ if (requestData.resourceType) body.resourceType = requestData.resourceType;
508
+ if (requestData.resource) body.resource = requestData.resource;
509
+ if (requestData.jurisdiction) body.jurisdiction = requestData.jurisdiction;
510
+ if (requestData.transactionValue) body.transactionValue = requestData.transactionValue;
511
+ if (requestData.currency) body.currency = requestData.currency;
512
+ if (requestData.isSubAgentRequest) body.isSubAgentRequest = requestData.isSubAgentRequest;
513
+ if (requestData.parentAgentId) body.parentAgentId = requestData.parentAgentId;
514
+ if (requestData.subAgentDepth !== void 0) body.subAgentDepth = requestData.subAgentDepth;
515
+ if (requestData.enableRuntimeChallenge)
516
+ body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;
517
+ if (requestData.createSession) body.createSession = requestData.createSession;
518
+ if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;
519
+ if (requestData.counterpartyType) body.counterpartyType = requestData.counterpartyType;
520
+ if (requestData.counterpartyUrl) body.counterpartyUrl = requestData.counterpartyUrl;
521
+ if (config.counterpartyId) body.counterpartyId = config.counterpartyId;
522
+ if (requestData.runtimeChallengeOptions)
523
+ body.runtimeChallengeOptions = requestData.runtimeChallengeOptions;
524
+ if (requestData.invocationProtocol) body.invocationProtocol = requestData.invocationProtocol;
525
+ if (requestData.commerceArtifacts) body.commerceArtifacts = requestData.commerceArtifacts;
526
+ if (requestData.attemptId) body.attemptId = requestData.attemptId;
527
+ if (requestData.considerationSet) body.considerationSet = requestData.considerationSet;
528
+ body.sdkVersion = SDK_VERSION;
529
+ if (requestData.callerMetadata || requestData.clientIp || requestData.userAgent) {
530
+ const meta = {
531
+ ...requestData.clientIp && { sourceIp: requestData.clientIp },
532
+ ...requestData.userAgent && { userAgent: requestData.userAgent },
533
+ ...requestData.callerMetadata
534
+ };
535
+ if (Object.keys(meta).length > 0) body.callerMetadata = meta;
536
+ }
537
+ const headers = {
538
+ "Content-Type": "application/json",
539
+ ...config.customHeaders
540
+ };
541
+ if (config.apiKey) {
542
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
543
+ headers["X-API-Key"] = config.apiKey;
544
+ }
545
+ try {
546
+ const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
547
+ method: "POST",
548
+ headers,
549
+ body: JSON.stringify(body),
550
+ signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
551
+ });
552
+ const data = await response.json();
553
+ if (response.status === 410) {
554
+ return {
555
+ success: true,
556
+ // 3.12.0 (Trust Record): keep the server-echoed attempt handle even on
557
+ // the synthesised 410 deny shape (echoed on every branch).
558
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
559
+ access: {
560
+ allowed: false,
561
+ accessLevel: "none",
562
+ reason: "endpoint_deactivated",
563
+ failures: [
564
+ {
565
+ dimension: "endpoint.deactivated",
566
+ message: typeof data?.message === "string" ? data.message : "Endpoint has been deactivated",
567
+ guidance: typeof data?.guidance === "string" ? data.guidance : "Reactivate via POST /api/endpoints/{id}/reactivate, or update the URL on the calling agent."
568
+ }
569
+ ]
570
+ }
571
+ };
572
+ }
573
+ if (!response.ok) {
574
+ if (response.status === 401) {
575
+ return {
576
+ success: false,
577
+ error: data.message || data.error || "Integration API key rejected",
578
+ misconfig: true,
579
+ misconfigCode: typeof data?.code === "string" ? data.code : void 0,
580
+ remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
581
+ correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
582
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
583
+ };
584
+ }
585
+ return {
586
+ success: false,
587
+ error: data.message || data.error || `API returned ${response.status}`,
588
+ correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
589
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
590
+ };
591
+ }
592
+ return data;
593
+ } catch (error) {
594
+ const message = error instanceof Error ? error.message : "Unknown error";
595
+ return {
596
+ success: false,
597
+ error: `Failed to call verify-access API: ${message}`
598
+ };
599
+ }
600
+ }
601
+ async function verify(config, request, options) {
602
+ const mergedConfig = { ...DEFAULT_CONFIG, ...config };
603
+ const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
604
+ if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
605
+ if (mergedConfig.strictInit) {
606
+ await performInitCheck(
607
+ mergedConfig.apiBaseUrl,
608
+ mergedConfig.debug,
609
+ true,
610
+ mergedConfig.apiKey
611
+ );
612
+ } else {
613
+ void performInitCheck(
614
+ mergedConfig.apiBaseUrl,
615
+ mergedConfig.debug,
616
+ false,
617
+ mergedConfig.apiKey
618
+ );
619
+ }
620
+ }
621
+ if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
622
+ deprecationWarningShown = true;
623
+ console.warn(
624
+ "[VerificationGateway] minTrustScore / minTrustScoreForFull are deprecated in v2.3.0 and have no effect. Server is now the single source of truth for access-level decisions (the SDK reads access.accessLevel from the verify-access response). To gate access to an endpoint, configure the endpoint's trust_score_requirement server-side."
625
+ );
626
+ }
627
+ if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache) {
628
+ const cached = getCachedResult(request, mergedConfig.counterpartyId);
629
+ if (cached) {
630
+ if (mergedConfig.debug) {
631
+ console.log("[VerificationGateway] Returning cached result");
632
+ }
633
+ const hit = { ...cached };
634
+ if (request.attemptId) {
635
+ hit.attemptId = request.attemptId;
636
+ } else {
637
+ delete hit.attemptId;
638
+ }
639
+ return hit;
640
+ }
641
+ }
642
+ const enrichedRequest = { ...request };
643
+ if (!enrichedRequest.counterpartyUrl && mergedConfig.counterpartyUrl) {
644
+ enrichedRequest.counterpartyUrl = mergedConfig.counterpartyUrl;
645
+ }
646
+ if (!enrichedRequest.counterpartyType && mergedConfig.counterpartyType) {
647
+ enrichedRequest.counterpartyType = mergedConfig.counterpartyType;
648
+ }
649
+ if (mergedConfig.debug) {
650
+ console.log("[VerificationGateway] Calling verify-access API");
651
+ }
652
+ const apiResponse = await callVerifyAccessAPI(mergedConfig, enrichedRequest);
653
+ const rawServerDenialReasons = apiResponse.denialReasons;
654
+ const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
655
+ if (!apiResponse.success) {
656
+ return createGuidanceResponse(mergedConfig, apiResponse.error, {
657
+ source: apiResponse.misconfig ? "merchant_misconfig" : "api_error",
658
+ correlationId: apiResponse.correlationId,
659
+ attemptId: apiResponse.attemptId,
660
+ remediation: apiResponse.remediation,
661
+ urls
662
+ });
663
+ }
664
+ if (!apiResponse.access?.allowed) {
665
+ const aggregatedFailures = apiResponse.access?.failures;
666
+ const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
667
+ const result2 = {
668
+ identityVerified: idVerifiedFromBackend,
669
+ policyAllowed: false,
670
+ // v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.
671
+ // Pre-rename this hardcoded `'guidance'`, which conflated with the
672
+ // colocated `guidance: {...}` help-payload object below and let
673
+ // denied requests pass any route gated at `'guidance'` because
674
+ // `hasMinimumAccess('guidance', 'guidance') === true`. Adapters now
675
+ // ALSO short-circuit on `!identityVerified || !policyAllowed` before
676
+ // the gate check — belt-and-braces.
677
+ accessLevel: "none",
678
+ // 3.6.0: the server now emits top-level denialReasons on every deny
679
+ // path — prefer it verbatim; the failure-message reconstruction remains
680
+ // as the fallback for older backends.
681
+ denialReasons: serverDenialReasons ?? (aggregatedFailures && aggregatedFailures.length > 0 ? aggregatedFailures.map((f) => f.message) : apiResponse.access?.reason ? [apiResponse.access.reason] : ["Access denied"]),
682
+ failures: aggregatedFailures,
683
+ requiresStepUp: apiResponse.access?.requiresStepUp,
684
+ requiresApproval: apiResponse.access?.requiresApproval,
685
+ guidance: {
686
+ message: apiResponse.access?.reason || "Access denied by PDLSS policy",
687
+ registrationUrl: urls?.registrationUrl ?? "",
688
+ documentationUrl: urls?.documentationUrl ?? ""
689
+ },
690
+ verifiedAt: /* @__PURE__ */ new Date(),
691
+ // Extract sessionId so decisions can be recorded for denials too
692
+ sessionId: apiResponse.sessionId,
693
+ // Anonymous traffic has no session → correlationId is the per-attempt
694
+ // linking key (the sessionId-equivalent for anonymous callers).
695
+ correlationId: apiResponse.correlationId,
696
+ // 3.12.0 (Trust Record): server echoes the attempt-chain handle on
697
+ // every branch — denials included (the funnel outlives one deny).
698
+ attemptId: apiResponse.attemptId,
699
+ recommendation: apiResponse.recommendation,
700
+ recommendationReasons: apiResponse.recommendationReasons,
701
+ stepUpApproval: apiResponse.stepUpApproval,
702
+ settlement: apiResponse.settlement
703
+ };
704
+ return result2;
705
+ }
706
+ const agent = apiResponse.agent ? {
707
+ astraId: apiResponse.agent.astraId,
708
+ name: apiResponse.agent.name,
709
+ trustScore: apiResponse.agent.trustScore,
710
+ trustLevel: getTrustLevel(apiResponse.agent.trustScore),
711
+ blockchainVerified: apiResponse.agent.blockchainStatus === "verified",
712
+ status: apiResponse.agent.agentStatus
713
+ } : void 0;
714
+ const developer = apiResponse.developer ? {
715
+ astradId: apiResponse.developer.kyaOwnerId,
716
+ name: apiResponse.developer.fullName,
717
+ trustScore: apiResponse.developer.trustScore || 0,
718
+ verified: apiResponse.developer.identityVerified
719
+ } : void 0;
720
+ const organization = apiResponse.organization ? {
721
+ name: apiResponse.organization.name,
722
+ verified: apiResponse.organization.verified,
723
+ trustScore: apiResponse.organization.trustScore
724
+ } : void 0;
725
+ const verificationContext = apiResponse.verificationContext;
726
+ const accessLevel = apiResponse.access?.accessLevel ?? "standard";
727
+ const result = {
728
+ // Round-18 G4: backend allowed access. Identity is verified (we resolved
729
+ // the caller to an agent) and policy passed all gates. Read idVerified
730
+ // from verificationContext for symmetry with the deny branch; default true
731
+ // on success path since `access.allowed === true` implies identity was
732
+ // resolvable (anonymous-allow paths flow through createGuidanceResponse).
733
+ identityVerified: apiResponse.verificationContext?.idVerified !== false,
734
+ policyAllowed: true,
735
+ accessLevel,
736
+ agent,
737
+ developer,
738
+ organization,
739
+ appliedPolicy: apiResponse.access?.appliedPolicy,
740
+ verificationContext,
741
+ requiresStepUp: apiResponse.access?.requiresStepUp,
742
+ requiresApproval: apiResponse.access?.requiresApproval,
743
+ verifiedAt: /* @__PURE__ */ new Date(),
744
+ cacheTtl: mergedConfig.cacheTtl,
745
+ // Handshake Protocol v10 enhanced fields (present when backend returns them)
746
+ sessionId: apiResponse.sessionId,
747
+ // v2.3.10 (defect #34, round-4): anonymous responses surface correlationId
748
+ // (no session row exists for unverified callers).
749
+ correlationId: apiResponse.correlationId,
750
+ // 3.12.0 (Trust Record): server-echoed attempt-chain handle.
751
+ attemptId: apiResponse.attemptId,
752
+ runtimeChallenge: apiResponse.runtimeChallenge,
753
+ tokenGuidance: apiResponse.tokenGuidance,
754
+ recommendation: apiResponse.recommendation,
755
+ recommendationReasons: apiResponse.recommendationReasons,
756
+ warningHeader: apiResponse.warningHeader,
757
+ stepUpApproval: apiResponse.stepUpApproval,
758
+ settlement: apiResponse.settlement
759
+ };
760
+ if (result.recommendation === "deny") {
761
+ result.policyAllowed = false;
762
+ result.accessLevel = "none";
763
+ result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Access denied by AstraSync recommendation"];
764
+ result.guidance = result.runtimeChallenge ? {
765
+ message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
766
+ registrationUrl: urls?.registrationUrl ?? "",
767
+ documentationUrl: urls?.documentationUrl ?? ""
768
+ } : {
769
+ message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
770
+ registrationUrl: urls?.registrationUrl ?? "",
771
+ documentationUrl: urls?.documentationUrl ?? ""
772
+ };
773
+ } else if (result.recommendation === "step_up_required") {
774
+ result.requiresStepUp = true;
775
+ result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
776
+ }
777
+ const escalates = result.requiresStepUp === true || result.requiresApproval === true || result.recommendation === "step_up_required";
778
+ if (mergedConfig.cacheTtl !== 0 && result.recommendation !== "deny" && !escalates) {
779
+ cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
780
+ }
781
+ return result;
782
+ }
783
+ async function reportUnregisteredAttempt(config, data) {
784
+ const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
785
+ const { counterpartyUrl, ...rest } = data;
786
+ const body = {
787
+ ...rest,
788
+ ...counterpartyUrl && { counterpartyUrl },
789
+ ...config.counterpartyId && { counterpartyId: config.counterpartyId }
790
+ };
791
+ await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
792
+ method: "POST",
793
+ headers: { "Content-Type": "application/json" },
794
+ body: JSON.stringify(body)
795
+ }).catch(() => {
796
+ });
797
+ }
798
+
799
+ // src/edge-core/request.ts
800
+ function headerValue(req, name) {
801
+ return req.headers[name.toLowerCase()];
802
+ }
803
+ function requestUrl(req, host) {
804
+ const qs = req.querystring ? `?${req.querystring}` : "";
805
+ return `https://${host}${req.uri}${qs}`;
806
+ }
807
+ function buildObservedMetadata(req) {
808
+ const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(req.headers);
809
+ const connection = req.connection ?? {};
810
+ return {
811
+ headers,
812
+ ...apiKeyFormat && { apiKeyFormat },
813
+ ...platformHeaders && { platformHeaders },
814
+ ...Object.keys(connection).length > 0 && { connection },
815
+ schemaVersion: CAPTURE_SCHEMA_VERSION
816
+ };
817
+ }
818
+
819
+ // src/edge-core/detect.ts
820
+ var SD_JWT_PATTERN = /^eyJ[\w-]+\.[\w-]*\.[\w-]+~/;
821
+ function decodeBase64Url(seg) {
822
+ const b64 = seg.replace(/-/g, "+").replace(/_/g, "/");
823
+ const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
824
+ const binary = atob(padded);
825
+ const bytes = new Uint8Array(binary.length);
826
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
827
+ return new TextDecoder().decode(bytes);
828
+ }
829
+ function sniffJwtHeader(compact) {
830
+ try {
831
+ const seg = compact.split(".")[0];
832
+ return JSON.parse(decodeBase64Url(seg));
833
+ } catch {
834
+ return void 0;
835
+ }
836
+ }
837
+ function parseJsonBody(bodyText) {
838
+ if (!bodyText) return void 0;
839
+ try {
840
+ const parsed = JSON.parse(bodyText);
841
+ return typeof parsed === "object" && parsed !== null ? parsed : void 0;
842
+ } catch {
843
+ return void 0;
844
+ }
845
+ }
846
+ function detectCommerce(req, host) {
847
+ const url = requestUrl(req, host);
848
+ const method = req.method;
849
+ const headers = req.headers;
850
+ const jsonBody = parseJsonBody(req.bodyText);
851
+ const truncated = req.bodyTruncated ? { bodyTruncated: true } : {};
852
+ const authHeader = headerValue(req, "authorization");
853
+ const bearer = authHeader?.replace(/^Bearer\s+/i, "");
854
+ const mandateHeader = headerValue(req, "x-payment-mandate") ?? headerValue(req, "payment-mandate");
855
+ const sdJwtCandidate = [mandateHeader, bearer].find((v) => v && SD_JWT_PATTERN.test(v));
856
+ if (sdJwtCandidate) {
857
+ const typ = sniffJwtHeader(sdJwtCandidate)?.typ;
858
+ return {
859
+ protocol: "vi-sd-jwt",
860
+ artifacts: { viSdJwt: sdJwtCandidate },
861
+ evidence: { typ, ...truncated }
862
+ };
863
+ }
864
+ const signature = headerValue(req, "signature");
865
+ const signatureInput = headerValue(req, "signature-input");
866
+ if (signature && signatureInput) {
867
+ const signatureAgent = headerValue(req, "signature-agent");
868
+ const registryHint = headerValue(req, "x-agent-registry");
869
+ const lowerInput = signatureInput.toLowerCase();
870
+ const protocol = signatureAgent ? "rfc9421-web-bot-auth" : registryHint?.toLowerCase().includes("visa") || lowerInput.includes("visa") ? "rfc9421-tap" : registryHint?.toLowerCase().includes("mastercard") || lowerInput.includes("mastercard") ? "rfc9421-agent-pay" : "rfc9421";
871
+ return {
872
+ protocol,
873
+ artifacts: {
874
+ rfc9421: { headers, method, url, ...req.bodyText && { body: req.bodyText } }
875
+ },
876
+ evidence: {
877
+ ...signatureAgent && { signatureAgent },
878
+ ...registryHint && { registryHint },
879
+ keyid: signatureInput.match(/keyid="([^"]+)"/)?.[1],
880
+ ...truncated
881
+ }
882
+ };
883
+ }
884
+ const acpTimestamp = headerValue(req, "timestamp");
885
+ if (signature && !signatureInput && (acpTimestamp || headerValue(req, "idempotency-key"))) {
886
+ return {
887
+ protocol: "acp",
888
+ artifacts: {
889
+ acpRequest: {
890
+ method,
891
+ url,
892
+ headers,
893
+ ...req.bodyText && { rawBody: req.bodyText },
894
+ ...jsonBody && { body: jsonBody }
895
+ }
896
+ },
897
+ evidence: { hasTimestamp: Boolean(acpTimestamp), ...truncated }
898
+ };
899
+ }
900
+ if (/\/checkout[-_]sessions(\/|$)/.test(req.uri)) {
901
+ return {
902
+ protocol: "ucp",
903
+ artifacts: { ucpRequest: { method, url, ...jsonBody && { body: jsonBody } } },
904
+ evidence: { path: req.uri, ...truncated }
905
+ };
906
+ }
907
+ if (jsonBody) {
908
+ const intent = jsonBody.intent_mandate ?? jsonBody.intentMandate;
909
+ const cart = jsonBody.cart_mandate ?? jsonBody.cartMandate;
910
+ const payment = jsonBody.payment_mandate ?? jsonBody.paymentMandate;
911
+ if (typeof intent === "string" || typeof cart === "string" || typeof payment === "string") {
912
+ return {
913
+ protocol: "ap2",
914
+ artifacts: {
915
+ ap2Mandates: {
916
+ ...typeof intent === "string" && { intent },
917
+ ...typeof cart === "string" && { cart },
918
+ ...typeof payment === "string" && { payment }
919
+ }
920
+ },
921
+ evidence: {
922
+ mandatesPresent: [
923
+ typeof intent === "string" && "intent",
924
+ typeof cart === "string" && "cart",
925
+ typeof payment === "string" && "payment"
926
+ ].filter(Boolean),
927
+ ...truncated
928
+ }
929
+ };
930
+ }
931
+ }
932
+ if (authHeader && /^Payment\s+/i.test(authHeader)) {
933
+ return {
934
+ protocol: "mpp",
935
+ artifacts: {
936
+ mppRequest: {
937
+ method,
938
+ url,
939
+ headers,
940
+ ...req.bodyText && { rawBody: req.bodyText },
941
+ ...jsonBody && { body: jsonBody }
942
+ }
943
+ },
944
+ evidence: truncated
945
+ };
946
+ }
947
+ if (headerValue(req, "x-payment")) {
948
+ return {
949
+ protocol: "x402",
950
+ artifacts: {
951
+ x402Request: { method, url, headers, ...jsonBody && { body: jsonBody } }
952
+ },
953
+ evidence: truncated
954
+ };
955
+ }
956
+ return void 0;
957
+ }
958
+
959
+ // src/edge-core/classify.ts
960
+ var STATIC_EXTENSIONS = /\.(css|js|mjs|map|png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|mp4|webm|pdf|txt|xml)$/i;
961
+ var BOT_UA_PATTERN = /curl|wget|python-requests|python-urllib|node-fetch|axios|got|okhttp|go-http-client|libwww|httpie|java\/|ruby|php|scrapy|headless|phantomjs|selenium|playwright|puppeteer|bot|crawler|spider|slurp/i;
962
+ var BROWSER_UA_PATTERN = /mozilla\/\d/i;
963
+ function resolvePolicy(config, path) {
964
+ if (STATIC_EXTENSIONS.test(path)) {
965
+ return { mode: config.mode, depth: config.depth, skip: true };
966
+ }
967
+ const rule = matchEdgePathRule(config.pathRules, path);
968
+ if (!rule) return { mode: config.mode, depth: config.depth, skip: false };
969
+ return {
970
+ mode: rule.mode ?? config.mode,
971
+ depth: rule.depth ?? config.depth,
972
+ skip: rule.skip === true
973
+ };
974
+ }
975
+ function classify(req, host, depth) {
976
+ const userAgent = headerValue(req, "user-agent") ?? "";
977
+ const credentials = extractCredentials(req.headers, {});
978
+ if (credentials.astraId || credentials.apiKey || credentials.jwt) {
979
+ return { tier: "verified", protocol: "x-astra" };
980
+ }
981
+ const { apiKeyFormat, platformHeaders } = sanitizeHeaders(req.headers);
982
+ const detectionInput = { userAgent, apiKeyFormat, platformHeaders };
983
+ if (depth !== "classify") {
984
+ const commerce = detectCommerce(req, host);
985
+ if (commerce) {
986
+ return {
987
+ tier: "identified",
988
+ protocol: commerce.protocol,
989
+ artifacts: commerce.artifacts,
990
+ evidence: commerce.evidence,
991
+ fingerprint: detectPlatformFingerprint(detectionInput)
992
+ };
993
+ }
994
+ }
995
+ const fingerprint = detectPlatformFingerprint(detectionInput);
996
+ if (fingerprint) {
997
+ return { tier: "identified", protocol: "platform-ua", fingerprint };
998
+ }
999
+ if (!userAgent || BOT_UA_PATTERN.test(userAgent) || !BROWSER_UA_PATTERN.test(userAgent)) {
1000
+ return { tier: "anonymous-bot", evidence: { userAgent: userAgent || "(none)" } };
1001
+ }
1002
+ if (!headerValue(req, "sec-fetch-mode") && !headerValue(req, "accept-language")) {
1003
+ return { tier: "anonymous-bot", evidence: { userAgent, spoofedBrowser: true } };
1004
+ }
1005
+ return { tier: "human" };
1006
+ }
1007
+ var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN };
1008
+
1009
+ // src/edge-core/messaging.ts
1010
+ var PROTOCOL_MESSAGES = {
1011
+ "vi-sd-jwt": "This request presented Visa Intelligent Commerce credentials, but the agent is not verified with AstraSync. Register the agent to pair your VI credentials with a verified identity and mandate.",
1012
+ "rfc9421-agent-pay": "This request carried a Mastercard Agent Pay signature, but the agent is not verified with AstraSync. Register the agent to pair Agent Pay credentials with a verified identity and mandate.",
1013
+ "rfc9421-tap": "This request carried a Visa TAP signature, but the agent is not verified with AstraSync. Register the agent to pair TAP credentials with a verified identity and mandate.",
1014
+ "rfc9421-web-bot-auth": "This request carried a Web Bot Auth signature, but the agent is not verified with AstraSync. Register the agent to link your signed-agent identity to a verified principal.",
1015
+ rfc9421: "This request carried an RFC 9421 HTTP message signature, but the agent is not verified with AstraSync. Register the agent to link the signing identity to a verified principal.",
1016
+ acp: "This request used the Agentic Commerce Protocol, but the agent is not verified with AstraSync. Register the agent to transact on this property.",
1017
+ ucp: "This request used a UCP checkout session, but the agent is not verified with AstraSync. Register the agent to transact on this property.",
1018
+ ap2: "This request presented AP2 mandates, but the agent is not verified with AstraSync. Register the agent to pair your mandates with a verified identity.",
1019
+ mpp: "This request presented MPP payment credentials, but the agent is not verified with AstraSync. Register the agent to transact on this property.",
1020
+ x402: "This request used x402 payment headers, but the agent is not verified with AstraSync. Register the agent to transact on this property.",
1021
+ "platform-ua": "This traffic was identified as a platform AI agent without AstraSync credentials. Register the agent to access this property with a verified identity.",
1022
+ "x-astra": "The presented AstraSync credentials failed verification or were denied by this property\u2019s access policy."
1023
+ };
1024
+ function buildDenyBody(outcome, apiBaseUrl) {
1025
+ const origin = apiBaseUrl.replace(/\/api\/?$/, "");
1026
+ const protocolMessage = (outcome.protocol && PROTOCOL_MESSAGES[outcome.protocol]) ?? "This request could not be verified by AstraSync.";
1027
+ return {
1028
+ success: false,
1029
+ error: {
1030
+ // UNAUTHORIZED = identity failed (register/re-authenticate helps);
1031
+ // INSUFFICIENT_ACCESS = identity fine, policy denied (fix the mandate).
1032
+ code: outcome.tier === "verified" ? "INSUFFICIENT_ACCESS" : "UNAUTHORIZED",
1033
+ message: protocolMessage,
1034
+ ...outcome.protocol && { protocol: outcome.protocol },
1035
+ failures: outcome.reasons,
1036
+ ...outcome.correlationId && { correlationId: outcome.correlationId },
1037
+ guidance: {
1038
+ // `||` not `??`: verify() fills these with EMPTY STRINGS when the
1039
+ // well-known cache is cold — empty means "use the fallback".
1040
+ message: outcome.guidance?.message || "Register this agent with AstraSync to receive verifiable credentials.",
1041
+ registrationUrl: outcome.guidance?.registrationUrl || `${origin}/register`,
1042
+ documentationUrl: outcome.guidance?.documentationUrl || `${origin}/docs/edge-adapter`
1043
+ }
1044
+ }
1045
+ };
1046
+ }
1047
+ function buildManualReviewBody(outcome) {
1048
+ return {
1049
+ success: false,
1050
+ status: "manual_review",
1051
+ message: "This action requires human approval. Poll the step-up approval endpoint and retry once approved.",
1052
+ ...outcome.stepUp && { stepUpApproval: outcome.stepUp },
1053
+ ...outcome.correlationId && { correlationId: outcome.correlationId }
1054
+ };
1055
+ }
1056
+
1057
+ // src/edge-core/enforce.ts
1058
+ var ASAT_HEADERS = {
1059
+ trustScore: "X-AstraSync-Trust-Score",
1060
+ agentId: "X-AstraSync-Agent-Id",
1061
+ developerId: "X-AstraSync-Developer-Id",
1062
+ permissions: "X-AstraSync-Permissions",
1063
+ observedDecision: "X-AstraSync-Observed-Decision"
1064
+ };
1065
+ function passUntouched() {
1066
+ return { action: "pass", injectHeaders: {}, stripHeaderPrefixes: [] };
1067
+ }
1068
+ function applyOutcome(options, outcome, policy) {
1069
+ const injectHeaders = {};
1070
+ if (outcome.misconfig) {
1071
+ injectHeaders[ASAT_HEADERS.observedDecision] = "misconfig";
1072
+ }
1073
+ if (policy.mode !== "enforce") {
1074
+ if (outcome.decision !== "allow") {
1075
+ injectHeaders[ASAT_HEADERS.observedDecision] = outcome.decision;
1076
+ }
1077
+ Object.assign(injectHeaders, asatHeaders(outcome));
1078
+ return { action: "pass", injectHeaders, stripHeaderPrefixes: ["x-astrasync-"] };
1079
+ }
1080
+ switch (outcome.decision) {
1081
+ case "deny":
1082
+ return {
1083
+ action: "respond",
1084
+ status: 403,
1085
+ headers: {},
1086
+ bodyJson: buildDenyBody(outcome, options.apiBaseUrl)
1087
+ };
1088
+ case "manual_review":
1089
+ return {
1090
+ action: "respond",
1091
+ status: 202,
1092
+ headers: { "Retry-After": "30" },
1093
+ bodyJson: buildManualReviewBody(outcome)
1094
+ };
1095
+ default:
1096
+ Object.assign(injectHeaders, asatHeaders(outcome));
1097
+ return { action: "pass", injectHeaders, stripHeaderPrefixes: ["x-astrasync-"] };
1098
+ }
1099
+ }
1100
+ function asatHeaders(outcome) {
1101
+ if (!outcome.asat) return {};
1102
+ const { trustScore, agentId, developerId, accessLevel } = outcome.asat;
1103
+ return {
1104
+ ...trustScore !== void 0 && { [ASAT_HEADERS.trustScore]: String(trustScore) },
1105
+ ...agentId && { [ASAT_HEADERS.agentId]: agentId },
1106
+ ...developerId && { [ASAT_HEADERS.developerId]: developerId },
1107
+ ...accessLevel && { [ASAT_HEADERS.permissions]: accessLevel }
1108
+ };
1109
+ }
1110
+
1111
+ // src/adapters/http-pdlss.ts
1112
+ var HTTP_METHOD_ACTION_TABLE = {
1113
+ GET: "data.read",
1114
+ HEAD: "data.read",
1115
+ OPTIONS: "data.read",
1116
+ POST: "data.write",
1117
+ PUT: "data.write",
1118
+ PATCH: "data.write",
1119
+ DELETE: "data.delete"
1120
+ };
1121
+ var DEFAULT_HTTP_ACTION = "data.write";
1122
+ var DEFAULT_HTTP_PURPOSE = "data";
1123
+ function actionForHttpMethod(method) {
1124
+ return HTTP_METHOD_ACTION_TABLE[method.toUpperCase()] ?? DEFAULT_HTTP_ACTION;
1125
+ }
1126
+ function normalizePurposeHeader(value) {
1127
+ const colon = value.indexOf(":");
1128
+ if (colon >= 0) {
1129
+ return { purpose: value.slice(0, colon) };
1130
+ }
1131
+ const dot = value.indexOf(".");
1132
+ if (dot > 0 && dot < value.length - 1) {
1133
+ return { purpose: value.slice(0, dot), actionCandidate: value };
1134
+ }
1135
+ return { purpose: value };
1136
+ }
1137
+ function resolveHttpPdlss(input) {
1138
+ const fromHeader = input.astraPurpose ? normalizePurposeHeader(input.astraPurpose) : void 0;
1139
+ let action;
1140
+ let actionSource;
1141
+ if (input.routeAction) {
1142
+ action = input.routeAction;
1143
+ actionSource = "route_config";
1144
+ } else if (input.hasCustomActionExtractor && input.customAction) {
1145
+ action = input.customAction;
1146
+ actionSource = "custom_extractor";
1147
+ } else if (!input.hasCustomActionExtractor && input.astraAction) {
1148
+ action = input.astraAction;
1149
+ actionSource = "header";
1150
+ } else if (!input.hasCustomActionExtractor && fromHeader?.actionCandidate) {
1151
+ action = fromHeader.actionCandidate;
1152
+ actionSource = "purpose_header_derived";
1153
+ } else {
1154
+ action = actionForHttpMethod(input.method);
1155
+ actionSource = "method_table";
1156
+ }
1157
+ let purpose;
1158
+ let purposeSource;
1159
+ if (input.routePurpose) {
1160
+ purpose = input.routePurpose;
1161
+ purposeSource = "route_config";
1162
+ } else if (input.hasCustomPurposeExtractor) {
1163
+ if (input.customPurpose) {
1164
+ purpose = input.customPurpose;
1165
+ purposeSource = "custom_extractor";
1166
+ }
1167
+ } else if (fromHeader) {
1168
+ purpose = fromHeader.purpose;
1169
+ purposeSource = "header";
1170
+ } else if (input.legacyPurpose) {
1171
+ purpose = input.legacyPurpose;
1172
+ purposeSource = "legacy_header";
1173
+ } else if (input.queryPurpose) {
1174
+ purpose = input.queryPurpose;
1175
+ purposeSource = "query";
1176
+ }
1177
+ if (!purpose) {
1178
+ const dot = action.indexOf(".");
1179
+ if (dot > 0) {
1180
+ purpose = action.slice(0, dot);
1181
+ purposeSource = "action_derived";
1182
+ } else {
1183
+ purpose = DEFAULT_HTTP_PURPOSE;
1184
+ purposeSource = "transport_default";
1185
+ }
1186
+ }
1187
+ return { purpose, action, purposeSource, actionSource };
1188
+ }
1189
+
1190
+ // src/edge-core/events.ts
1191
+ async function withBudget(promise, ms) {
1192
+ let timer;
1193
+ try {
1194
+ return await Promise.race([
1195
+ promise,
1196
+ new Promise((resolve) => {
1197
+ timer = setTimeout(() => resolve(void 0), ms);
1198
+ })
1199
+ ]);
1200
+ } catch {
1201
+ return void 0;
1202
+ } finally {
1203
+ if (timer) clearTimeout(timer);
1204
+ }
1205
+ }
1206
+ async function emitBeacon(options, req, classification, budgetMs) {
1207
+ const budgeted = withBudget(
1208
+ reportUnregisteredAttempt(
1209
+ { apiBaseUrl: options.apiBaseUrl, counterpartyId: options.counterpartyId },
1210
+ {
1211
+ counterpartyUrl: options.counterpartyUrl,
1212
+ counterpartyType: "website",
1213
+ sourceIp: req.clientIp,
1214
+ userAgent: headerValue(req, "user-agent"),
1215
+ requestPath: req.uri,
1216
+ requestMethod: req.method,
1217
+ // Evidence fields the backend persists into the event record.
1218
+ protocol: classification.protocol,
1219
+ evidence: classification.evidence,
1220
+ // The full sanitized header + connection capture for the
1221
+ // unregistered caller (fire-and-forget, so free).
1222
+ observedMetadata: buildObservedMetadata(req)
1223
+ }
1224
+ ),
1225
+ budgetMs
1226
+ );
1227
+ if (options.waitUntil) {
1228
+ options.waitUntil(budgeted);
1229
+ return;
1230
+ }
1231
+ await budgeted;
1232
+ }
1233
+ function sampled(rate, random = Math.random) {
1234
+ if (rate <= 0) return false;
1235
+ if (rate >= 1) return true;
1236
+ return random() < rate;
1237
+ }
1238
+
1239
+ // src/edge-core/pipeline.ts
1240
+ function baseOutcome(classification) {
1241
+ return {
1242
+ tier: classification.tier,
1243
+ decision: "allow",
1244
+ reasons: [],
1245
+ protocol: classification.protocol
1246
+ };
1247
+ }
1248
+ function buildVerificationRequest(options, req, classification, includeArtifacts) {
1249
+ const pdlss = resolveHttpPdlss({
1250
+ method: req.method,
1251
+ astraPurpose: headerValue(req, "x-astra-purpose"),
1252
+ astraAction: headerValue(req, "x-astra-action")
1253
+ });
1254
+ const attemptId = headerValue(req, "x-astra-attempt-id");
1255
+ return {
1256
+ ...attemptId && /^att_[0-9a-f]{32}$/.test(attemptId) && { attemptId },
1257
+ credentials: extractCredentials(req.headers, {}),
1258
+ purpose: pdlss.purpose,
1259
+ action: pdlss.action,
1260
+ resource: req.uri,
1261
+ createSession: true,
1262
+ counterpartyType: "website",
1263
+ counterpartyUrl: options.counterpartyUrl,
1264
+ callerMetadata: {
1265
+ sourceIp: req.clientIp,
1266
+ userAgent: headerValue(req, "user-agent"),
1267
+ referer: headerValue(req, "referer"),
1268
+ host: headerValue(req, "host"),
1269
+ // The full sanitized header + connection capture. The backend stores
1270
+ // it fire-and-forget (zero decision latency) and uses the key-format
1271
+ // / platform headers for attribution.
1272
+ observedMetadata: buildObservedMetadata(req)
1273
+ },
1274
+ ...includeArtifacts && classification.artifacts && { commerceArtifacts: classification.artifacts }
1275
+ };
1276
+ }
1277
+ function outcomeFromVerifyResult(classification, result) {
1278
+ const outcome = baseOutcome(classification);
1279
+ outcome.sessionId = result.sessionId;
1280
+ outcome.correlationId = result.correlationId;
1281
+ outcome.guidance = result.guidance;
1282
+ if (result.misconfigured) {
1283
+ outcome.reasons = ["verification key misconfigured \u2014 fail-open (merchant_misconfig)"];
1284
+ outcome.misconfig = true;
1285
+ return outcome;
1286
+ }
1287
+ if (result.requiresStepUp || result.stepUpApproval) {
1288
+ outcome.decision = "manual_review";
1289
+ outcome.reasons = ["step-up approval required"];
1290
+ outcome.stepUp = {
1291
+ pollUrl: result.stepUpApproval?.pollUrl,
1292
+ expiresAt: result.stepUpApproval?.expiresAt
1293
+ };
1294
+ return outcome;
1295
+ }
1296
+ if (!result.identityVerified || !result.policyAllowed) {
1297
+ outcome.decision = "deny";
1298
+ outcome.reasons = result.denialReasons?.length ? result.denialReasons : [!result.identityVerified ? "identity not verified" : "denied by access policy"];
1299
+ return outcome;
1300
+ }
1301
+ outcome.asat = {
1302
+ trustScore: result.agent?.trustScore,
1303
+ agentId: result.agent?.astraId,
1304
+ developerId: result.developer?.astradId,
1305
+ accessLevel: result.accessLevel
1306
+ };
1307
+ return outcome;
1308
+ }
1309
+ async function runTier(options, req, classification, policy, apiKey, sampling) {
1310
+ const budgets = options.budgets ?? {};
1311
+ const beaconMs = budgets.beaconMs ?? 300;
1312
+ const verifyMs = policy.mode === "enforce" ? budgets.verifyEnforceMs ?? 3e3 : budgets.verifyObserveMs ?? 1500;
1313
+ const gatewayConfig = {
1314
+ apiBaseUrl: options.apiBaseUrl,
1315
+ ...apiKey && { apiKey },
1316
+ counterpartyId: options.counterpartyId,
1317
+ // The gateway handles its own error semantics; init self-probes don't
1318
+ // belong in a per-request edge function.
1319
+ disableInitChecks: true
1320
+ };
1321
+ try {
1322
+ switch (classification.tier) {
1323
+ case "verified": {
1324
+ if (policy.depth === "classify") {
1325
+ return baseOutcome(classification);
1326
+ }
1327
+ const result = await withBudget(
1328
+ verify(
1329
+ gatewayConfig,
1330
+ buildVerificationRequest(options, req, classification, policy.depth === "authorize")
1331
+ ),
1332
+ verifyMs
1333
+ );
1334
+ if (!result) {
1335
+ const outcome = baseOutcome(classification);
1336
+ outcome.reasons = ["verify-access unreachable within budget \u2014 fail-open"];
1337
+ return outcome;
1338
+ }
1339
+ return outcomeFromVerifyResult(classification, result);
1340
+ }
1341
+ case "identified": {
1342
+ if (policy.depth === "authorize" && classification.artifacts) {
1343
+ const result = await withBudget(
1344
+ verify(gatewayConfig, buildVerificationRequest(options, req, classification, true)),
1345
+ verifyMs
1346
+ );
1347
+ if (!result) {
1348
+ const outcome2 = baseOutcome(classification);
1349
+ outcome2.reasons = ["verify-access unreachable within budget \u2014 fail-open"];
1350
+ return outcome2;
1351
+ }
1352
+ return outcomeFromVerifyResult(classification, result);
1353
+ }
1354
+ await emitBeacon(options, req, classification, beaconMs);
1355
+ const outcome = baseOutcome(classification);
1356
+ if (policy.mode === "enforce" && policy.depth !== "classify") {
1357
+ outcome.decision = "deny";
1358
+ outcome.reasons = ["agent is not registered with AstraSync"];
1359
+ }
1360
+ return outcome;
1361
+ }
1362
+ case "anonymous-bot": {
1363
+ if (sampled(sampling.anonymousBeaconRate, options.random)) {
1364
+ await emitBeacon(
1365
+ options,
1366
+ req,
1367
+ { ...classification, protocol: classification.protocol ?? "ua-heuristic" },
1368
+ beaconMs
1369
+ );
1370
+ }
1371
+ return baseOutcome(classification);
1372
+ }
1373
+ default:
1374
+ return baseOutcome(classification);
1375
+ }
1376
+ } catch (err) {
1377
+ const outcome = baseOutcome(classification);
1378
+ outcome.reasons = [
1379
+ `edge pipeline error \u2014 fail-open: ${err instanceof Error ? err.message : String(err)}`
1380
+ ];
1381
+ return outcome;
1382
+ }
1383
+ }
1384
+
1385
+ // src/edge-core/evaluate.ts
1386
+ async function evaluateEdgeRequest(options, req, config, apiKey) {
1387
+ const policy = resolvePolicy(config, req.uri);
1388
+ try {
1389
+ if (policy.skip) return { policy, application: passUntouched() };
1390
+ const host = headerValue(req, "x-forwarded-host") ?? headerValue(req, "host") ?? new URL(options.counterpartyUrl).host;
1391
+ const classification = classify(req, host, policy.depth);
1392
+ if (classification.tier === "human") {
1393
+ return { policy, classification, application: passUntouched() };
1394
+ }
1395
+ const outcome = await runTier(options, req, classification, policy, apiKey, config.sampling);
1396
+ if (outcome.decision !== "allow") {
1397
+ options.log?.(
1398
+ "info",
1399
+ policy.mode === "enforce" ? `${outcome.decision.toUpperCase()}: ${req.method} ${req.uri}` : `[OBSERVE] would have been ${outcome.decision.toUpperCase()}: ${req.method} ${req.uri}`,
1400
+ { tier: outcome.tier, protocol: outcome.protocol, reasons: outcome.reasons }
1401
+ );
1402
+ }
1403
+ return {
1404
+ policy,
1405
+ classification,
1406
+ outcome,
1407
+ application: applyOutcome(options, outcome, policy)
1408
+ };
1409
+ } catch (err) {
1410
+ options.log?.("error", "edge evaluation error \u2014 passing request through", {
1411
+ error: err instanceof Error ? err.message : String(err)
1412
+ });
1413
+ return { policy, application: passUntouched() };
1414
+ }
1415
+ }
1416
+ export {
1417
+ ASAT_HEADERS,
1418
+ _patterns,
1419
+ applyOutcome,
1420
+ buildDenyBody,
1421
+ buildManualReviewBody,
1422
+ buildObservedMetadata,
1423
+ buildVerificationRequest,
1424
+ classify,
1425
+ detectCommerce,
1426
+ emitBeacon,
1427
+ evaluateEdgeRequest,
1428
+ headerValue,
1429
+ outcomeFromVerifyResult,
1430
+ passUntouched,
1431
+ requestUrl,
1432
+ resolvePolicy,
1433
+ runTier,
1434
+ sampled,
1435
+ withBudget
1436
+ };
1437
+ //# sourceMappingURL=index.mjs.map