@astrasyncai/verification-gateway 5.14.0 → 5.15.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 (98) hide show
  1. package/README.md +27 -17
  2. package/dist/adapters/express.d.mts +1 -1
  3. package/dist/adapters/express.d.ts +1 -1
  4. package/dist/adapters/express.js +477 -240
  5. package/dist/adapters/express.js.map +1 -1
  6. package/dist/adapters/express.mjs +477 -240
  7. package/dist/adapters/express.mjs.map +1 -1
  8. package/dist/adapters/mcp.d.mts +1 -1
  9. package/dist/adapters/mcp.d.ts +1 -1
  10. package/dist/adapters/mcp.js +20 -6
  11. package/dist/adapters/mcp.js.map +1 -1
  12. package/dist/adapters/mcp.mjs +20 -6
  13. package/dist/adapters/mcp.mjs.map +1 -1
  14. package/dist/adapters/nextjs.d.mts +1 -1
  15. package/dist/adapters/nextjs.d.ts +1 -1
  16. package/dist/adapters/nextjs.js +460 -239
  17. package/dist/adapters/nextjs.js.map +1 -1
  18. package/dist/adapters/nextjs.mjs +460 -239
  19. package/dist/adapters/nextjs.mjs.map +1 -1
  20. package/dist/adapters/sdk.d.mts +18 -10
  21. package/dist/adapters/sdk.d.ts +18 -10
  22. package/dist/adapters/sdk.js +80 -23
  23. package/dist/adapters/sdk.js.map +1 -1
  24. package/dist/adapters/sdk.mjs +80 -23
  25. package/dist/adapters/sdk.mjs.map +1 -1
  26. package/dist/agent/index.js +1 -1
  27. package/dist/agent/index.js.map +1 -1
  28. package/dist/agent/index.mjs +1 -1
  29. package/dist/agent/index.mjs.map +1 -1
  30. package/dist/bin/astrasync-claude-hook.js +55 -11
  31. package/dist/bin/astrasync-codex-hook.js +55 -11
  32. package/dist/bin/astrasync-guard.js +55 -11
  33. package/dist/bin/astrasync.js +288 -33
  34. package/dist/browser/background.js +50 -6
  35. package/dist/browser/background.js.map +1 -1
  36. package/dist/browser/background.mjs +60 -6
  37. package/dist/browser/background.mjs.map +1 -1
  38. package/dist/cli/index.js +1 -1
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/cli/index.mjs +1 -1
  41. package/dist/cli/index.mjs.map +1 -1
  42. package/dist/codex/index.js +55 -11
  43. package/dist/codex/index.js.map +1 -1
  44. package/dist/codex/index.mjs +65 -11
  45. package/dist/codex/index.mjs.map +1 -1
  46. package/dist/cursor/extension.js +55 -11
  47. package/dist/cursor/extension.js.map +1 -1
  48. package/dist/cursor/extension.mjs +65 -11
  49. package/dist/cursor/extension.mjs.map +1 -1
  50. package/dist/edge-config.d.mts +31 -2
  51. package/dist/edge-config.d.ts +31 -2
  52. package/dist/edge-config.js +35 -5
  53. package/dist/edge-config.js.map +1 -1
  54. package/dist/edge-config.mjs +32 -4
  55. package/dist/edge-config.mjs.map +1 -1
  56. package/dist/edge-core/index.d.mts +11 -2
  57. package/dist/edge-core/index.d.ts +11 -2
  58. package/dist/edge-core/index.js +93 -10
  59. package/dist/edge-core/index.js.map +1 -1
  60. package/dist/edge-core/index.mjs +91 -10
  61. package/dist/edge-core/index.mjs.map +1 -1
  62. package/dist/gateway/gateway.js +50 -6
  63. package/dist/gateway/gateway.js.map +1 -1
  64. package/dist/gateway/gateway.mjs +60 -6
  65. package/dist/gateway/gateway.mjs.map +1 -1
  66. package/dist/git-trigger/git-hooks.d.mts +1 -1
  67. package/dist/git-trigger/git-hooks.d.ts +1 -1
  68. package/dist/index.d.mts +1314 -34
  69. package/dist/index.d.ts +1314 -34
  70. package/dist/index.js +5116 -2612
  71. package/dist/index.js.map +1 -1
  72. package/dist/index.mjs +5063 -2614
  73. package/dist/index.mjs.map +1 -1
  74. package/dist/registration/index.d.mts +28 -2
  75. package/dist/registration/index.d.ts +28 -2
  76. package/dist/registration/index.js +18 -3
  77. package/dist/registration/index.js.map +1 -1
  78. package/dist/registration/index.mjs +15 -2
  79. package/dist/registration/index.mjs.map +1 -1
  80. package/dist/transport/index.d.mts +7 -5
  81. package/dist/transport/index.d.ts +7 -5
  82. package/dist/transport/index.js +61 -5
  83. package/dist/transport/index.js.map +1 -1
  84. package/dist/transport/index.mjs +67 -11
  85. package/dist/transport/index.mjs.map +1 -1
  86. package/dist/{types-C_b4QTyO.d.ts → types-DtJjlW4O.d.ts} +104 -10
  87. package/dist/{types-BCkwBA84.d.mts → types-Dxf9DBpr.d.mts} +106 -12
  88. package/dist/{types-BCkwBA84.d.ts → types-Dxf9DBpr.d.ts} +106 -12
  89. package/dist/{types-DlshIIR-.d.mts → types-Eg6VoBNH.d.mts} +104 -10
  90. package/dist/ui/index.d.mts +1 -1
  91. package/dist/ui/index.d.ts +1 -1
  92. package/dist/verify.d.mts +1 -1
  93. package/dist/verify.d.ts +1 -1
  94. package/dist/verify.js +20 -6
  95. package/dist/verify.js.map +1 -1
  96. package/dist/verify.mjs +20 -6
  97. package/dist/verify.mjs.map +1 -1
  98. package/package.json +1 -1
@@ -1,6 +1,28 @@
1
- import { G as GatewayConfig } from './types-DlshIIR-.mjs';
1
+ import { G as GatewayConfig } from './types-Eg6VoBNH.mjs';
2
2
  import './metadata-capture.mjs';
3
3
 
4
+ /**
5
+ * Route pattern compiler shared by every matcher of endpoint route policy:
6
+ * the express and Next.js middleware, the edge config and the AstraSync
7
+ * bridge (round 27.9 — they each had their own copy, and none understood
8
+ * path parameters).
9
+ *
10
+ * Pattern syntax:
11
+ * - `*` any characters, including `/` (unchanged since v1)
12
+ * - `:name` exactly one path segment (no `/`), e.g.
13
+ * `/ucp/v1/checkout-sessions/:id/complete`
14
+ * - anything else is literal — `.`, `?`, `+`, `(`… are escaped (they used
15
+ * to be live regex syntax, so `/robots.txt` also matched `/robotsXtxt`).
16
+ * Anchored `^…$`. Case-sensitive unless `caseInsensitive` is set.
17
+ */
18
+ declare function routePatternToRegExp(pattern: string, opts?: {
19
+ caseInsensitive?: boolean;
20
+ }): RegExp | null;
21
+ /** True when `path` matches `pattern` (see routePatternToRegExp). */
22
+ declare function matchRoutePattern(pattern: string, path: string, opts?: {
23
+ caseInsensitive?: boolean;
24
+ }): boolean;
25
+
4
26
  /**
5
27
  * Edge verification config — the per-endpoint policy the Trusted Agent
6
28
  * Gateway edge adapters (e.g. `@astrasyncai/adapter-lambda`) fetch from the
@@ -100,6 +122,13 @@ interface EdgeConfig {
100
122
  * endpoint owner explicitly configures this.
101
123
  */
102
124
  queryValueAllowlist?: string[];
125
+ /**
126
+ * 5.15.0: minimum TLS version for AGENT traffic (UCP 2026-08-25 requires
127
+ * TLS 1.3). Read from CloudFront-Viewer-TLS; follows the path's mode —
128
+ * enforce answers 426 Upgrade Required, observe only logs. Human browsers
129
+ * are never gated, and an unknown version never blocks. ABSENT = no check.
130
+ */
131
+ minTls?: '1.2' | '1.3';
103
132
  /**
104
133
  * 4.5.0: the EFFECTIVE failure posture is derived from the effective
105
134
  * mode — fail posture follows success posture. Observe fails open
@@ -173,4 +202,4 @@ declare function matchEdgePathRule(rules: EdgePathRule[], path: string): EdgePat
173
202
  /** Reset cache — for testing only. */
174
203
  declare function _resetEdgeConfigCache(): void;
175
204
 
176
- export { DEFAULT_EDGE_CONFIG, type EdgeConfig, type EdgeMode, type EdgePathRule, type EdgeVerificationDepth, type FetchEdgeConfigResult, type FetchEdgeConfigSuccess, _resetEdgeConfigCache, degradeToObserve, fetchEdgeConfig, getEdgeConfig, isEdgeConfig, matchEdgePathRule };
205
+ export { DEFAULT_EDGE_CONFIG, type EdgeConfig, type EdgeMode, type EdgePathRule, type EdgeVerificationDepth, type FetchEdgeConfigResult, type FetchEdgeConfigSuccess, _resetEdgeConfigCache, degradeToObserve, fetchEdgeConfig, getEdgeConfig, isEdgeConfig, matchEdgePathRule, matchRoutePattern, routePatternToRegExp };
@@ -1,6 +1,28 @@
1
- import { G as GatewayConfig } from './types-C_b4QTyO.js';
1
+ import { G as GatewayConfig } from './types-DtJjlW4O.js';
2
2
  import './metadata-capture.js';
3
3
 
4
+ /**
5
+ * Route pattern compiler shared by every matcher of endpoint route policy:
6
+ * the express and Next.js middleware, the edge config and the AstraSync
7
+ * bridge (round 27.9 — they each had their own copy, and none understood
8
+ * path parameters).
9
+ *
10
+ * Pattern syntax:
11
+ * - `*` any characters, including `/` (unchanged since v1)
12
+ * - `:name` exactly one path segment (no `/`), e.g.
13
+ * `/ucp/v1/checkout-sessions/:id/complete`
14
+ * - anything else is literal — `.`, `?`, `+`, `(`… are escaped (they used
15
+ * to be live regex syntax, so `/robots.txt` also matched `/robotsXtxt`).
16
+ * Anchored `^…$`. Case-sensitive unless `caseInsensitive` is set.
17
+ */
18
+ declare function routePatternToRegExp(pattern: string, opts?: {
19
+ caseInsensitive?: boolean;
20
+ }): RegExp | null;
21
+ /** True when `path` matches `pattern` (see routePatternToRegExp). */
22
+ declare function matchRoutePattern(pattern: string, path: string, opts?: {
23
+ caseInsensitive?: boolean;
24
+ }): boolean;
25
+
4
26
  /**
5
27
  * Edge verification config — the per-endpoint policy the Trusted Agent
6
28
  * Gateway edge adapters (e.g. `@astrasyncai/adapter-lambda`) fetch from the
@@ -100,6 +122,13 @@ interface EdgeConfig {
100
122
  * endpoint owner explicitly configures this.
101
123
  */
102
124
  queryValueAllowlist?: string[];
125
+ /**
126
+ * 5.15.0: minimum TLS version for AGENT traffic (UCP 2026-08-25 requires
127
+ * TLS 1.3). Read from CloudFront-Viewer-TLS; follows the path's mode —
128
+ * enforce answers 426 Upgrade Required, observe only logs. Human browsers
129
+ * are never gated, and an unknown version never blocks. ABSENT = no check.
130
+ */
131
+ minTls?: '1.2' | '1.3';
103
132
  /**
104
133
  * 4.5.0: the EFFECTIVE failure posture is derived from the effective
105
134
  * mode — fail posture follows success posture. Observe fails open
@@ -173,4 +202,4 @@ declare function matchEdgePathRule(rules: EdgePathRule[], path: string): EdgePat
173
202
  /** Reset cache — for testing only. */
174
203
  declare function _resetEdgeConfigCache(): void;
175
204
 
176
- export { DEFAULT_EDGE_CONFIG, type EdgeConfig, type EdgeMode, type EdgePathRule, type EdgeVerificationDepth, type FetchEdgeConfigResult, type FetchEdgeConfigSuccess, _resetEdgeConfigCache, degradeToObserve, fetchEdgeConfig, getEdgeConfig, isEdgeConfig, matchEdgePathRule };
205
+ export { DEFAULT_EDGE_CONFIG, type EdgeConfig, type EdgeMode, type EdgePathRule, type EdgeVerificationDepth, type FetchEdgeConfigResult, type FetchEdgeConfigSuccess, _resetEdgeConfigCache, degradeToObserve, fetchEdgeConfig, getEdgeConfig, isEdgeConfig, matchEdgePathRule, matchRoutePattern, routePatternToRegExp };
@@ -26,12 +26,14 @@ __export(edge_config_exports, {
26
26
  fetchEdgeConfig: () => fetchEdgeConfig,
27
27
  getEdgeConfig: () => getEdgeConfig,
28
28
  isEdgeConfig: () => isEdgeConfig,
29
- matchEdgePathRule: () => matchEdgePathRule
29
+ matchEdgePathRule: () => matchEdgePathRule,
30
+ matchRoutePattern: () => matchRoutePattern,
31
+ routePatternToRegExp: () => routePatternToRegExp
30
32
  });
31
33
  module.exports = __toCommonJS(edge_config_exports);
32
34
 
33
35
  // src/version.ts
34
- var SDK_VERSION = "5.14.0";
36
+ var SDK_VERSION = "5.15.0";
35
37
 
36
38
  // src/http.ts
37
39
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -51,6 +53,32 @@ function withUserAgent(initHeaders) {
51
53
  }
52
54
  var sdkFetch = (input, init) => fetch(input, { ...init, headers: withUserAgent(init?.headers) });
53
55
 
56
+ // src/route-pattern.ts
57
+ function routePatternToRegExp(pattern, opts = {}) {
58
+ let source = "";
59
+ for (let i = 0; i < pattern.length; i++) {
60
+ const ch = pattern[i];
61
+ if (ch === "*") {
62
+ source += ".*";
63
+ } else if (ch === ":" && /[A-Za-z_]/.test(pattern[i + 1] ?? "") && (i === 0 || pattern[i - 1] === "/")) {
64
+ let j = i + 1;
65
+ while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) j++;
66
+ source += "[^/]+";
67
+ i = j - 1;
68
+ } else {
69
+ source += ch.replace(/[.+?^${}()|[\]\\/]/g, "\\$&");
70
+ }
71
+ }
72
+ try {
73
+ return new RegExp(`^${source}$`, opts.caseInsensitive ? "i" : "");
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+ function matchRoutePattern(pattern, path, opts = {}) {
79
+ return routePatternToRegExp(pattern, opts)?.test(path) ?? false;
80
+ }
81
+
54
82
  // src/edge-config.ts
55
83
  var DEFAULT_EDGE_CONFIG = Object.freeze({
56
84
  version: 1,
@@ -83,6 +111,7 @@ function isEdgeConfig(value) {
83
111
  if (r.sampling !== void 0 && !isSamplingShape(r.sampling)) return false;
84
112
  }
85
113
  if (!isSamplingShape(v.sampling)) return false;
114
+ if (v.minTls !== void 0 && v.minTls !== "1.2" && v.minTls !== "1.3") return false;
86
115
  if (v.queryValueAllowlist !== void 0) {
87
116
  if (!Array.isArray(v.queryValueAllowlist) || v.queryValueAllowlist.length > 16 || !v.queryValueAllowlist.every((e) => typeof e === "string" && e.length > 0 && e.length <= 64)) {
88
117
  return false;
@@ -173,8 +202,7 @@ function matchEdgePathRule(rules, path) {
173
202
  return void 0;
174
203
  }
175
204
  function globToRegExp(pattern) {
176
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
177
- return new RegExp(`^${escaped}$`, "i");
205
+ return routePatternToRegExp(pattern, { caseInsensitive: true }) ?? /$^/;
178
206
  }
179
207
  function _resetEdgeConfigCache() {
180
208
  cache.clear();
@@ -188,6 +216,8 @@ function _resetEdgeConfigCache() {
188
216
  fetchEdgeConfig,
189
217
  getEdgeConfig,
190
218
  isEdgeConfig,
191
- matchEdgePathRule
219
+ matchEdgePathRule,
220
+ matchRoutePattern,
221
+ routePatternToRegExp
192
222
  });
193
223
  //# sourceMappingURL=edge-config.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/edge-config.ts","../src/version.ts","../src/http.ts"],"sourcesContent":["/**\n * Edge verification config — the per-endpoint policy the Trusted Agent\n * Gateway edge adapters (e.g. `@astrasyncai/adapter-lambda`) fetch from the\n * AstraSync dashboard to decide how much verification friction to apply\n * to inbound traffic, and where.\n *\n * Fetch pattern mirrors `well-known.ts`: TTL cache + stale-while-revalidate\n * + in-flight dedupe, with a `_reset*` hook for tests. Fail posture is\n * deliberately asymmetric: this module can NEVER put a site into enforce\n * mode on its own — enforce only ever originates from a successfully\n * fetched config, and a config that has gone stale past `maxStaleMs`\n * degrades back to observe.\n *\n * Which EdgeConfig fields each integration consumes:\n *\n * | Field | Edge adapters | SDK adapters (express/nextjs/mcp) |\n * | ------------------------------ | ------------- | --------------------------------- |\n * | `mode` (observe/enforce) | yes | — |\n * | `depth` | yes | — |\n * | `pathRules` | yes | — |\n * | `sampling.anonymousBeaconRate` | yes | yes — the SDK anonymous beacon |\n * | `failurePosture` | yes | — |\n *\n * The observe/enforce × depth posture is EDGE-ONLY by design: SDK adapters\n * enforce via the dashboard route policy (`fetchRoutes`), not via this\n * config, and consume only the `sampling` block for their anonymous\n * traffic beacon.\n */\n\nimport { sdkFetch } from './http';\nimport type { GatewayConfig } from './types';\n\n/**\n * Edge posture. Canonical Trusted Agent Gateway vocabulary:\n * `observe` ≈ the gateway's `passive` posture (verify +\n * record, enforce nothing), `enforce` ≈ `active`. New value set on a new\n * field name — do NOT reuse `GatewayPosture`'s `active`/`passive` here\n * (different field, different meaning: posture governs an agent-side\n * gateway; mode governs counterparty-side edge interception).\n */\nexport type EdgeMode = 'observe' | 'enforce';\n\n/**\n * How deep the edge checks each request — what is it → who is it → are\n * they allowed:\n * - `classify` — UA / platform-fingerprint classification only; no\n * credential handling.\n * - `authenticate` — establish who is calling: detect and parse agent\n * credentials (X-Astra-*, VI SD-JWT, RFC 9421, ACP,\n * UCP, AP2…) and verify presented AstraSync identities.\n * - `authorize` — full policy decision: credentials cryptographically\n * proven and PDLSS-evaluated via a verify-access round\n * trip. (US spelling on the wire, matching\n * `authorizeSettlement` and HTTP `Authorization`.)\n */\nexport type EdgeVerificationDepth = 'classify' | 'authenticate' | 'authorize';\n\nexport interface EdgePathRule {\n /**\n * `*`-wildcard glob matched against the request path. Matching is\n * case-INsensitive and all regex metacharacters except `*` are\n * escaped. (Deliberately diverges from the express adapter's\n * `matchRoute`, which is historically case-sensitive by default —\n * new surface ships case-insensitive matching as the default.)\n */\n pattern: string;\n /** Pass the request through untouched (static assets); mode/depth ignored. */\n skip?: boolean;\n /** Override the top-level mode for this path. */\n mode?: EdgeMode;\n /** Override the top-level depth for this path. */\n depth?: EdgeVerificationDepth;\n /**\n * Per-path beacon sampling opt-DOWN (VI uplift 1.2). The default is full\n * capture; put a fractional rate on explicitly high-volume/low-value paths\n * (static mirrors, health checks) instead of sampling commerce traffic.\n */\n sampling?: {\n /** Fraction (0..1) of anonymous-bot requests that emit a beacon. */\n anonymousBeaconRate: number;\n };\n}\n\nexport interface EdgeConfig {\n /** Shape version — bump on breaking changes. */\n version: 1;\n /** Default posture for paths no rule matches. */\n mode: EdgeMode;\n /** Default check depth for paths no rule matches. */\n depth: EdgeVerificationDepth;\n /** Ordered, first-match-wins. */\n pathRules: EdgePathRule[];\n sampling: {\n /** Fraction (0..1) of anonymous-bot requests that emit a beacon. */\n anonymousBeaconRate: number;\n };\n /**\n * Query-string parameter names whose VALUES may be captured into\n * `observedMetadata.queryParams` (VI uplift Part 4 — campaign/UTM\n * attribution). Lowercase exact names or a single trailing-`*` glob\n * (`utm_*`); ≤16 entries × ≤64 chars. ABSENT by default — the privacy\n * stance (query values dropped at capture) is unchanged unless the\n * endpoint owner explicitly configures this.\n */\n queryValueAllowlist?: string[];\n /**\n * 4.5.0: the EFFECTIVE failure posture is derived from the effective\n * mode — fail posture follows success posture. Observe fails open\n * (unreachable backend = pass-through, site never breaks); enforce fails\n * closed on verification-infrastructure failure (503\n * `VERIFICATION_UNAVAILABLE`, retry-later — see edge-core/pipeline). This\n * field is retained on the wire for adapter compatibility and as a future\n * explicit override; it is not consulted by the 4.5.x pipeline. Note the\n * distinction from CONFIG staleness: a config stale past 24h still has\n * enforce degraded to observe (`degradeToObserve`) — that is config-trust\n * hygiene, independent of runtime verification availability.\n */\n failurePosture: 'open' | 'closed';\n}\n\n/**\n * The safe default: observe-only, classification depth, nothing enforced.\n * Served by the backend when an endpoint has never been configured, and\n * used by `getEdgeConfig` whenever no config can be fetched.\n *\n * Beacon rate 1.0 (VI uplift 1.2): every anonymous hit beacons by default —\n * a sampled denominator understates anonymous traffic in every dashboard\n * tile and no toggle can reveal the dropped rows. Sampling is a deliberate\n * per-path opt-down (`EdgePathRule.sampling`) for static/high-volume paths.\n */\nexport const DEFAULT_EDGE_CONFIG: EdgeConfig = Object.freeze({\n version: 1,\n mode: 'observe',\n depth: 'classify',\n pathRules: [],\n sampling: Object.freeze({ anonymousBeaconRate: 1.0 }),\n failurePosture: 'open',\n}) as EdgeConfig;\n\nconst EDGE_MODES: readonly string[] = ['observe', 'enforce'];\nconst EDGE_DEPTHS: readonly string[] = ['classify', 'authenticate', 'authorize'];\n\nfunction isSamplingShape(value: unknown): value is { anonymousBeaconRate: number } {\n if (typeof value !== 'object' || value === null) return false;\n const rate = (value as Record<string, unknown>).anonymousBeaconRate;\n return typeof rate === 'number' && rate >= 0 && rate <= 1;\n}\n\n/** Structural guard for configs arriving over the wire. */\nexport function isEdgeConfig(value: unknown): value is EdgeConfig {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n if (v.version !== 1) return false;\n if (!EDGE_MODES.includes(v.mode as string)) return false;\n if (!EDGE_DEPTHS.includes(v.depth as string)) return false;\n if (!Array.isArray(v.pathRules)) return false;\n for (const rule of v.pathRules) {\n if (typeof rule !== 'object' || rule === null) return false;\n const r = rule as Record<string, unknown>;\n if (typeof r.pattern !== 'string' || r.pattern.length === 0) return false;\n if (r.mode !== undefined && !EDGE_MODES.includes(r.mode as string)) return false;\n if (r.depth !== undefined && !EDGE_DEPTHS.includes(r.depth as string)) return false;\n if (r.sampling !== undefined && !isSamplingShape(r.sampling)) return false;\n }\n if (!isSamplingShape(v.sampling)) return false;\n if (v.queryValueAllowlist !== undefined) {\n if (\n !Array.isArray(v.queryValueAllowlist) ||\n v.queryValueAllowlist.length > 16 ||\n !v.queryValueAllowlist.every((e) => typeof e === 'string' && e.length > 0 && e.length <= 64)\n ) {\n return false;\n }\n }\n return v.failurePosture === 'open';\n}\n\nexport interface FetchEdgeConfigSuccess {\n edgeConfig: EdgeConfig;\n etag?: string;\n}\n\nexport type FetchEdgeConfigResult = FetchEdgeConfigSuccess | { notModified: true } | null;\n\n/**\n * Fetch the edge config for an endpoint from the AstraSync backend.\n * `GET {apiBaseUrl}/endpoints/:counterpartyId/edge-config` with the same\n * auth headers as `fetchRoutes`. Pass the cached `etag` to get a cheap\n * `{ notModified: true }` on 304. Returns `null` on any failure —\n * the caller decides how to fall back.\n */\nexport async function fetchEdgeConfig(\n config: Pick<GatewayConfig, 'apiBaseUrl' | 'apiKey'>,\n counterpartyId: string,\n opts?: { etag?: string; timeoutMs?: number }\n): Promise<FetchEdgeConfigResult> {\n if (!counterpartyId) return null;\n const headers: Record<string, string> = { Accept: 'application/json' };\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n headers['X-API-Key'] = config.apiKey;\n }\n if (opts?.etag) headers['If-None-Match'] = opts.etag;\n try {\n const response = await sdkFetch(\n `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,\n { method: 'GET', headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5000) }\n );\n if (response.status === 304) return { notModified: true };\n if (!response.ok) return null;\n const body = (await response.json()) as { data?: { edgeConfig?: unknown } };\n const edgeConfig = body.data?.edgeConfig;\n if (!isEdgeConfig(edgeConfig)) return null;\n return { edgeConfig, etag: response.headers.get('etag') ?? undefined };\n } catch {\n return null;\n }\n}\n\ninterface CacheEntry {\n config: EdgeConfig;\n etag?: string;\n /** When the entry was last confirmed fresh (fetch or 304). */\n lastSuccessAt: number;\n}\n\nconst DEFAULT_TTL_MS = 60 * 1000;\nconst DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1000;\n\nconst cache = new Map<string, CacheEntry>();\nconst inflight = new Map<string, Promise<void>>();\n\nfunction cacheKey(apiBaseUrl: string, counterpartyId: string): string {\n return `${apiBaseUrl}|${counterpartyId}`;\n}\n\n/** Copy of `config` with every enforce (top-level and per-rule) degraded to observe. */\nexport function degradeToObserve(config: EdgeConfig): EdgeConfig {\n if (config.mode !== 'enforce' && !config.pathRules.some((r) => r.mode === 'enforce')) {\n return config;\n }\n return {\n ...config,\n mode: 'observe',\n pathRules: config.pathRules.map((r) => (r.mode === 'enforce' ? { ...r, mode: 'observe' } : r)),\n };\n}\n\nasync function revalidate(\n config: Pick<GatewayConfig, 'apiBaseUrl' | 'apiKey'>,\n counterpartyId: string\n): Promise<void> {\n const key = cacheKey(config.apiBaseUrl, counterpartyId);\n const existing = inflight.get(key);\n if (existing) return existing;\n\n const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache.get(key)?.etag })\n .then((result) => {\n if (result === null) return; // failure — cache untouched, caller falls back\n const now = Date.now();\n if ('notModified' in result) {\n const entry = cache.get(key);\n if (entry) entry.lastSuccessAt = now;\n return;\n }\n cache.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });\n })\n .finally(() => {\n inflight.delete(key);\n });\n\n inflight.set(key, promise);\n return promise;\n}\n\n/**\n * Get the edge config for an endpoint, cached. Never throws, never blocks\n * longer than one fetch.\n *\n * - Fresh cache (within `ttlMs`) → cached config, no network.\n * - Stale cache → cached config immediately + background revalidate\n * (stale-while-revalidate). If the last successful fetch is older than\n * `maxStaleMs` (default 24h), any enforce is degraded to observe — a\n * long-dead backend can never keep a site enforcing.\n * - No cache → await one fetch; on failure return `DEFAULT_EDGE_CONFIG`\n * (observe-only). Defaults never enforce.\n */\nexport async function getEdgeConfig(\n config: Pick<GatewayConfig, 'apiBaseUrl' | 'apiKey'>,\n counterpartyId: string,\n opts?: { ttlMs?: number; maxStaleMs?: number }\n): Promise<EdgeConfig> {\n const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;\n const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;\n const key = cacheKey(config.apiBaseUrl, counterpartyId);\n\n const entry = cache.get(key);\n if (entry) {\n const age = Date.now() - entry.lastSuccessAt;\n if (age > ttlMs) {\n revalidate(config, counterpartyId).catch(() => {\n // background refresh failed — stale entry remains until next call\n });\n }\n return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;\n }\n\n await revalidate(config, counterpartyId).catch(() => {\n // fetch failed — fall through to defaults\n });\n return cache.get(key)?.config ?? DEFAULT_EDGE_CONFIG;\n}\n\n/**\n * First-match-wins path-rule lookup. Case-insensitive `*` globs with all\n * other regex metacharacters escaped (a literal `.` in a pattern matches\n * only `.`). Returns `undefined` when no rule matches — the caller applies\n * the config's top-level mode/depth.\n */\nexport function matchEdgePathRule(rules: EdgePathRule[], path: string): EdgePathRule | undefined {\n for (const rule of rules) {\n if (globToRegExp(rule.pattern).test(path)) return rule;\n }\n return undefined;\n}\n\nfunction globToRegExp(pattern: string): RegExp {\n const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '.*');\n return new RegExp(`^${escaped}$`, 'i');\n}\n\n/** Reset cache — for testing only. */\nexport function _resetEdgeConfigCache(): void {\n cache.clear();\n inflight.clear();\n}\n","/**\n * Single source-of-truth for the SDK's\n * package version emitted on verify-access bodies (and any future\n * telemetry). Bumped alongside `package.json#version` on every release.\n *\n * Why a constant rather than `import pkg from '../package.json'`:\n * - `tsconfig.json` sets `rootDir: ./src`; importing the sibling\n * package.json fails the build with \"outside rootDir\".\n * - Build-time string replacement (tsup `define`, esbuild banner, etc.)\n * adds toolchain coupling for a trivial gain.\n * - Embedded readonly constant works in every environment (Node, browser,\n * bundlers, Deno) without runtime fs / network access.\n *\n * Release discipline: a CI lint can grep `package.json#version` against\n * this constant if the two ever diverge in the wild. A manual bump\n * is fine — bumping both in the release-ceremony commit keeps them\n * lockstep.\n */\nexport const SDK_VERSION = '5.14.0';\n","/**\n * Shared outbound-HTTP wrapper — every request the SDK makes to the\n * AstraSync backend (or anywhere else) goes through `sdkFetch` so it\n * carries an identifying User-Agent.\n *\n * Why: Node's global fetch defaults to `user-agent: node`, which is\n * indistinguishable from every other Node client on the wire. Visit\n * Intelligence observed our own SDK/beacon traffic as anonymous \"node\"\n * visits. Self-identification is the platform's own medicine.\n *\n * Browser note: some browsers filter the User-Agent request header\n * silently (never throwing), so this is a no-op there — which is correct:\n * in a browser the page's real UA is the honest identity.\n */\n\nimport { SDK_VERSION } from './version';\n\nexport const SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;\n\n/**\n * Add the SDK User-Agent without changing the SHAPE of the caller's headers\n * (plain records stay plain records) — interceptors, adapters, and tests\n * read `init.headers` as a record, and `Headers` would also lowercase names.\n */\nfunction withUserAgent(initHeaders: HeadersInit | undefined): HeadersInit {\n if (!initHeaders) return { 'user-agent': SDK_USER_AGENT };\n if (initHeaders instanceof Headers) {\n const copy = new Headers(initHeaders);\n if (!copy.has('user-agent')) copy.set('user-agent', SDK_USER_AGENT);\n return copy;\n }\n if (Array.isArray(initHeaders)) {\n const has = initHeaders.some(([name]) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : [...initHeaders, ['user-agent', SDK_USER_AGENT]];\n }\n const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : { 'user-agent': SDK_USER_AGENT, ...initHeaders };\n}\n\nexport const sdkFetch: typeof fetch = (input, init) =>\n fetch(input, { ...init, headers: withUserAgent(init?.headers) });\n"],"mappings":"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","names":["has"]}
1
+ {"version":3,"sources":["../src/edge-config.ts","../src/version.ts","../src/http.ts","../src/route-pattern.ts"],"sourcesContent":["/**\n * Edge verification config — the per-endpoint policy the Trusted Agent\n * Gateway edge adapters (e.g. `@astrasyncai/adapter-lambda`) fetch from the\n * AstraSync dashboard to decide how much verification friction to apply\n * to inbound traffic, and where.\n *\n * Fetch pattern mirrors `well-known.ts`: TTL cache + stale-while-revalidate\n * + in-flight dedupe, with a `_reset*` hook for tests. Fail posture is\n * deliberately asymmetric: this module can NEVER put a site into enforce\n * mode on its own — enforce only ever originates from a successfully\n * fetched config, and a config that has gone stale past `maxStaleMs`\n * degrades back to observe.\n *\n * Which EdgeConfig fields each integration consumes:\n *\n * | Field | Edge adapters | SDK adapters (express/nextjs/mcp) |\n * | ------------------------------ | ------------- | --------------------------------- |\n * | `mode` (observe/enforce) | yes | — |\n * | `depth` | yes | — |\n * | `pathRules` | yes | — |\n * | `sampling.anonymousBeaconRate` | yes | yes — the SDK anonymous beacon |\n * | `failurePosture` | yes | — |\n *\n * The observe/enforce × depth posture is EDGE-ONLY by design: SDK adapters\n * enforce via the dashboard route policy (`fetchRoutes`), not via this\n * config, and consume only the `sampling` block for their anonymous\n * traffic beacon.\n */\n\nimport { sdkFetch } from './http';\nimport type { GatewayConfig } from './types';\nimport { routePatternToRegExp } from './route-pattern';\n\n/**\n * Edge posture. Canonical Trusted Agent Gateway vocabulary:\n * `observe` ≈ the gateway's `passive` posture (verify +\n * record, enforce nothing), `enforce` ≈ `active`. New value set on a new\n * field name — do NOT reuse `GatewayPosture`'s `active`/`passive` here\n * (different field, different meaning: posture governs an agent-side\n * gateway; mode governs counterparty-side edge interception).\n */\nexport type EdgeMode = 'observe' | 'enforce';\n\n/**\n * How deep the edge checks each request — what is it → who is it → are\n * they allowed:\n * - `classify` — UA / platform-fingerprint classification only; no\n * credential handling.\n * - `authenticate` — establish who is calling: detect and parse agent\n * credentials (X-Astra-*, VI SD-JWT, RFC 9421, ACP,\n * UCP, AP2…) and verify presented AstraSync identities.\n * - `authorize` — full policy decision: credentials cryptographically\n * proven and PDLSS-evaluated via a verify-access round\n * trip. (US spelling on the wire, matching\n * `authorizeSettlement` and HTTP `Authorization`.)\n */\nexport type EdgeVerificationDepth = 'classify' | 'authenticate' | 'authorize';\n\nexport interface EdgePathRule {\n /**\n * `*`-wildcard glob matched against the request path. Matching is\n * case-INsensitive and all regex metacharacters except `*` are\n * escaped. (Deliberately diverges from the express adapter's\n * `matchRoute`, which is historically case-sensitive by default —\n * new surface ships case-insensitive matching as the default.)\n */\n pattern: string;\n /** Pass the request through untouched (static assets); mode/depth ignored. */\n skip?: boolean;\n /** Override the top-level mode for this path. */\n mode?: EdgeMode;\n /** Override the top-level depth for this path. */\n depth?: EdgeVerificationDepth;\n /**\n * Per-path beacon sampling opt-DOWN (VI uplift 1.2). The default is full\n * capture; put a fractional rate on explicitly high-volume/low-value paths\n * (static mirrors, health checks) instead of sampling commerce traffic.\n */\n sampling?: {\n /** Fraction (0..1) of anonymous-bot requests that emit a beacon. */\n anonymousBeaconRate: number;\n };\n}\n\nexport interface EdgeConfig {\n /** Shape version — bump on breaking changes. */\n version: 1;\n /** Default posture for paths no rule matches. */\n mode: EdgeMode;\n /** Default check depth for paths no rule matches. */\n depth: EdgeVerificationDepth;\n /** Ordered, first-match-wins. */\n pathRules: EdgePathRule[];\n sampling: {\n /** Fraction (0..1) of anonymous-bot requests that emit a beacon. */\n anonymousBeaconRate: number;\n };\n /**\n * Query-string parameter names whose VALUES may be captured into\n * `observedMetadata.queryParams` (VI uplift Part 4 — campaign/UTM\n * attribution). Lowercase exact names or a single trailing-`*` glob\n * (`utm_*`); ≤16 entries × ≤64 chars. ABSENT by default — the privacy\n * stance (query values dropped at capture) is unchanged unless the\n * endpoint owner explicitly configures this.\n */\n queryValueAllowlist?: string[];\n /**\n * 5.15.0: minimum TLS version for AGENT traffic (UCP 2026-08-25 requires\n * TLS 1.3). Read from CloudFront-Viewer-TLS; follows the path's mode —\n * enforce answers 426 Upgrade Required, observe only logs. Human browsers\n * are never gated, and an unknown version never blocks. ABSENT = no check.\n */\n minTls?: '1.2' | '1.3';\n /**\n * 4.5.0: the EFFECTIVE failure posture is derived from the effective\n * mode — fail posture follows success posture. Observe fails open\n * (unreachable backend = pass-through, site never breaks); enforce fails\n * closed on verification-infrastructure failure (503\n * `VERIFICATION_UNAVAILABLE`, retry-later — see edge-core/pipeline). This\n * field is retained on the wire for adapter compatibility and as a future\n * explicit override; it is not consulted by the 4.5.x pipeline. Note the\n * distinction from CONFIG staleness: a config stale past 24h still has\n * enforce degraded to observe (`degradeToObserve`) — that is config-trust\n * hygiene, independent of runtime verification availability.\n */\n failurePosture: 'open' | 'closed';\n}\n\n/**\n * The safe default: observe-only, classification depth, nothing enforced.\n * Served by the backend when an endpoint has never been configured, and\n * used by `getEdgeConfig` whenever no config can be fetched.\n *\n * Beacon rate 1.0 (VI uplift 1.2): every anonymous hit beacons by default —\n * a sampled denominator understates anonymous traffic in every dashboard\n * tile and no toggle can reveal the dropped rows. Sampling is a deliberate\n * per-path opt-down (`EdgePathRule.sampling`) for static/high-volume paths.\n */\nexport const DEFAULT_EDGE_CONFIG: EdgeConfig = Object.freeze({\n version: 1,\n mode: 'observe',\n depth: 'classify',\n pathRules: [],\n sampling: Object.freeze({ anonymousBeaconRate: 1.0 }),\n failurePosture: 'open',\n}) as EdgeConfig;\n\nconst EDGE_MODES: readonly string[] = ['observe', 'enforce'];\nconst EDGE_DEPTHS: readonly string[] = ['classify', 'authenticate', 'authorize'];\n\nfunction isSamplingShape(value: unknown): value is { anonymousBeaconRate: number } {\n if (typeof value !== 'object' || value === null) return false;\n const rate = (value as Record<string, unknown>).anonymousBeaconRate;\n return typeof rate === 'number' && rate >= 0 && rate <= 1;\n}\n\n/** Structural guard for configs arriving over the wire. */\nexport function isEdgeConfig(value: unknown): value is EdgeConfig {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n if (v.version !== 1) return false;\n if (!EDGE_MODES.includes(v.mode as string)) return false;\n if (!EDGE_DEPTHS.includes(v.depth as string)) return false;\n if (!Array.isArray(v.pathRules)) return false;\n for (const rule of v.pathRules) {\n if (typeof rule !== 'object' || rule === null) return false;\n const r = rule as Record<string, unknown>;\n if (typeof r.pattern !== 'string' || r.pattern.length === 0) return false;\n if (r.mode !== undefined && !EDGE_MODES.includes(r.mode as string)) return false;\n if (r.depth !== undefined && !EDGE_DEPTHS.includes(r.depth as string)) return false;\n if (r.sampling !== undefined && !isSamplingShape(r.sampling)) return false;\n }\n if (!isSamplingShape(v.sampling)) return false;\n if (v.minTls !== undefined && v.minTls !== '1.2' && v.minTls !== '1.3') return false;\n if (v.queryValueAllowlist !== undefined) {\n if (\n !Array.isArray(v.queryValueAllowlist) ||\n v.queryValueAllowlist.length > 16 ||\n !v.queryValueAllowlist.every((e) => typeof e === 'string' && e.length > 0 && e.length <= 64)\n ) {\n return false;\n }\n }\n return v.failurePosture === 'open';\n}\n\nexport interface FetchEdgeConfigSuccess {\n edgeConfig: EdgeConfig;\n etag?: string;\n}\n\nexport type FetchEdgeConfigResult = FetchEdgeConfigSuccess | { notModified: true } | null;\n\n/**\n * Fetch the edge config for an endpoint from the AstraSync backend.\n * `GET {apiBaseUrl}/endpoints/:counterpartyId/edge-config` with the same\n * auth headers as `fetchRoutes`. Pass the cached `etag` to get a cheap\n * `{ notModified: true }` on 304. Returns `null` on any failure —\n * the caller decides how to fall back.\n */\nexport async function fetchEdgeConfig(\n config: Pick<GatewayConfig, 'apiBaseUrl' | 'apiKey'>,\n counterpartyId: string,\n opts?: { etag?: string; timeoutMs?: number }\n): Promise<FetchEdgeConfigResult> {\n if (!counterpartyId) return null;\n const headers: Record<string, string> = { Accept: 'application/json' };\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n headers['X-API-Key'] = config.apiKey;\n }\n if (opts?.etag) headers['If-None-Match'] = opts.etag;\n try {\n const response = await sdkFetch(\n `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,\n { method: 'GET', headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5000) }\n );\n if (response.status === 304) return { notModified: true };\n if (!response.ok) return null;\n const body = (await response.json()) as { data?: { edgeConfig?: unknown } };\n const edgeConfig = body.data?.edgeConfig;\n if (!isEdgeConfig(edgeConfig)) return null;\n return { edgeConfig, etag: response.headers.get('etag') ?? undefined };\n } catch {\n return null;\n }\n}\n\ninterface CacheEntry {\n config: EdgeConfig;\n etag?: string;\n /** When the entry was last confirmed fresh (fetch or 304). */\n lastSuccessAt: number;\n}\n\nconst DEFAULT_TTL_MS = 60 * 1000;\nconst DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1000;\n\nconst cache = new Map<string, CacheEntry>();\nconst inflight = new Map<string, Promise<void>>();\n\nfunction cacheKey(apiBaseUrl: string, counterpartyId: string): string {\n return `${apiBaseUrl}|${counterpartyId}`;\n}\n\n/** Copy of `config` with every enforce (top-level and per-rule) degraded to observe. */\nexport function degradeToObserve(config: EdgeConfig): EdgeConfig {\n if (config.mode !== 'enforce' && !config.pathRules.some((r) => r.mode === 'enforce')) {\n return config;\n }\n return {\n ...config,\n mode: 'observe',\n pathRules: config.pathRules.map((r) => (r.mode === 'enforce' ? { ...r, mode: 'observe' } : r)),\n };\n}\n\nasync function revalidate(\n config: Pick<GatewayConfig, 'apiBaseUrl' | 'apiKey'>,\n counterpartyId: string\n): Promise<void> {\n const key = cacheKey(config.apiBaseUrl, counterpartyId);\n const existing = inflight.get(key);\n if (existing) return existing;\n\n const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache.get(key)?.etag })\n .then((result) => {\n if (result === null) return; // failure — cache untouched, caller falls back\n const now = Date.now();\n if ('notModified' in result) {\n const entry = cache.get(key);\n if (entry) entry.lastSuccessAt = now;\n return;\n }\n cache.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });\n })\n .finally(() => {\n inflight.delete(key);\n });\n\n inflight.set(key, promise);\n return promise;\n}\n\n/**\n * Get the edge config for an endpoint, cached. Never throws, never blocks\n * longer than one fetch.\n *\n * - Fresh cache (within `ttlMs`) → cached config, no network.\n * - Stale cache → cached config immediately + background revalidate\n * (stale-while-revalidate). If the last successful fetch is older than\n * `maxStaleMs` (default 24h), any enforce is degraded to observe — a\n * long-dead backend can never keep a site enforcing.\n * - No cache → await one fetch; on failure return `DEFAULT_EDGE_CONFIG`\n * (observe-only). Defaults never enforce.\n */\nexport async function getEdgeConfig(\n config: Pick<GatewayConfig, 'apiBaseUrl' | 'apiKey'>,\n counterpartyId: string,\n opts?: { ttlMs?: number; maxStaleMs?: number }\n): Promise<EdgeConfig> {\n const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;\n const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;\n const key = cacheKey(config.apiBaseUrl, counterpartyId);\n\n const entry = cache.get(key);\n if (entry) {\n const age = Date.now() - entry.lastSuccessAt;\n if (age > ttlMs) {\n revalidate(config, counterpartyId).catch(() => {\n // background refresh failed — stale entry remains until next call\n });\n }\n return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;\n }\n\n await revalidate(config, counterpartyId).catch(() => {\n // fetch failed — fall through to defaults\n });\n return cache.get(key)?.config ?? DEFAULT_EDGE_CONFIG;\n}\n\n/**\n * First-match-wins path-rule lookup. Case-insensitive `*` globs with all\n * other regex metacharacters escaped (a literal `.` in a pattern matches\n * only `.`). Returns `undefined` when no rule matches — the caller applies\n * the config's top-level mode/depth.\n */\nexport function matchEdgePathRule(rules: EdgePathRule[], path: string): EdgePathRule | undefined {\n for (const rule of rules) {\n if (globToRegExp(rule.pattern).test(path)) return rule;\n }\n return undefined;\n}\n\nfunction globToRegExp(pattern: string): RegExp {\n // 27.9: shared compiler (route-pattern.ts); the edge matches case-insensitively.\n return routePatternToRegExp(pattern, { caseInsensitive: true }) ?? /$^/;\n}\n\n/** Reset cache — for testing only. */\nexport function _resetEdgeConfigCache(): void {\n cache.clear();\n inflight.clear();\n}\n\n// 27.9: the shared route pattern compiler, re-exported on this subpath so\n// consumers (the AstraSync bridge) can import it without the package root.\nexport { routePatternToRegExp, matchRoutePattern } from './route-pattern';\n","/**\n * Single source-of-truth for the SDK's\n * package version emitted on verify-access bodies (and any future\n * telemetry). Bumped alongside `package.json#version` on every release.\n *\n * Why a constant rather than `import pkg from '../package.json'`:\n * - `tsconfig.json` sets `rootDir: ./src`; importing the sibling\n * package.json fails the build with \"outside rootDir\".\n * - Build-time string replacement (tsup `define`, esbuild banner, etc.)\n * adds toolchain coupling for a trivial gain.\n * - Embedded readonly constant works in every environment (Node, browser,\n * bundlers, Deno) without runtime fs / network access.\n *\n * Release discipline: a CI lint can grep `package.json#version` against\n * this constant if the two ever diverge in the wild. A manual bump\n * is fine — bumping both in the release-ceremony commit keeps them\n * lockstep.\n */\nexport const SDK_VERSION = '5.15.0';\n","/**\n * Shared outbound-HTTP wrapper — every request the SDK makes to the\n * AstraSync backend (or anywhere else) goes through `sdkFetch` so it\n * carries an identifying User-Agent.\n *\n * Why: Node's global fetch defaults to `user-agent: node`, which is\n * indistinguishable from every other Node client on the wire. Visit\n * Intelligence observed our own SDK/beacon traffic as anonymous \"node\"\n * visits. Self-identification is the platform's own medicine.\n *\n * Browser note: some browsers filter the User-Agent request header\n * silently (never throwing), so this is a no-op there — which is correct:\n * in a browser the page's real UA is the honest identity.\n */\n\nimport { SDK_VERSION } from './version';\n\nexport const SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;\n\n/**\n * Add the SDK User-Agent without changing the SHAPE of the caller's headers\n * (plain records stay plain records) — interceptors, adapters, and tests\n * read `init.headers` as a record, and `Headers` would also lowercase names.\n */\nfunction withUserAgent(initHeaders: HeadersInit | undefined): HeadersInit {\n if (!initHeaders) return { 'user-agent': SDK_USER_AGENT };\n if (initHeaders instanceof Headers) {\n const copy = new Headers(initHeaders);\n if (!copy.has('user-agent')) copy.set('user-agent', SDK_USER_AGENT);\n return copy;\n }\n if (Array.isArray(initHeaders)) {\n const has = initHeaders.some(([name]) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : [...initHeaders, ['user-agent', SDK_USER_AGENT]];\n }\n const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : { 'user-agent': SDK_USER_AGENT, ...initHeaders };\n}\n\nexport const sdkFetch: typeof fetch = (input, init) =>\n fetch(input, { ...init, headers: withUserAgent(init?.headers) });\n","/**\n * Route pattern compiler shared by every matcher of endpoint route policy:\n * the express and Next.js middleware, the edge config and the AstraSync\n * bridge (round 27.9 — they each had their own copy, and none understood\n * path parameters).\n *\n * Pattern syntax:\n * - `*` any characters, including `/` (unchanged since v1)\n * - `:name` exactly one path segment (no `/`), e.g.\n * `/ucp/v1/checkout-sessions/:id/complete`\n * - anything else is literal — `.`, `?`, `+`, `(`… are escaped (they used\n * to be live regex syntax, so `/robots.txt` also matched `/robotsXtxt`).\n * Anchored `^…$`. Case-sensitive unless `caseInsensitive` is set.\n */\nexport function routePatternToRegExp(\n pattern: string,\n opts: { caseInsensitive?: boolean } = {}\n): RegExp | null {\n let source = '';\n for (let i = 0; i < pattern.length; i++) {\n const ch = pattern[i];\n if (ch === '*') {\n source += '.*';\n } else if (\n ch === ':' &&\n /[A-Za-z_]/.test(pattern[i + 1] ?? '') &&\n (i === 0 || pattern[i - 1] === '/')\n ) {\n // `:name` at the start of a segment: consume the identifier.\n let j = i + 1;\n while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) j++;\n source += '[^/]+';\n i = j - 1;\n } else {\n source += ch.replace(/[.+?^${}()|[\\]\\\\/]/g, '\\\\$&');\n }\n }\n try {\n return new RegExp(`^${source}$`, opts.caseInsensitive ? 'i' : '');\n } catch {\n // A malformed stored pattern never matches (fail closed at the caller).\n return null;\n }\n}\n\n/** True when `path` matches `pattern` (see routePatternToRegExp). */\nexport function matchRoutePattern(\n pattern: string,\n path: string,\n opts: { caseInsensitive?: boolean } = {}\n): boolean {\n return routePatternToRegExp(pattern, opts)?.test(path) ?? false;\n}\n"],"mappings":"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","names":["has"]}
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var SDK_VERSION = "5.14.0";
2
+ var SDK_VERSION = "5.15.0";
3
3
 
4
4
  // src/http.ts
5
5
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -19,6 +19,32 @@ function withUserAgent(initHeaders) {
19
19
  }
20
20
  var sdkFetch = (input, init) => fetch(input, { ...init, headers: withUserAgent(init?.headers) });
21
21
 
22
+ // src/route-pattern.ts
23
+ function routePatternToRegExp(pattern, opts = {}) {
24
+ let source = "";
25
+ for (let i = 0; i < pattern.length; i++) {
26
+ const ch = pattern[i];
27
+ if (ch === "*") {
28
+ source += ".*";
29
+ } else if (ch === ":" && /[A-Za-z_]/.test(pattern[i + 1] ?? "") && (i === 0 || pattern[i - 1] === "/")) {
30
+ let j = i + 1;
31
+ while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) j++;
32
+ source += "[^/]+";
33
+ i = j - 1;
34
+ } else {
35
+ source += ch.replace(/[.+?^${}()|[\]\\/]/g, "\\$&");
36
+ }
37
+ }
38
+ try {
39
+ return new RegExp(`^${source}$`, opts.caseInsensitive ? "i" : "");
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+ function matchRoutePattern(pattern, path, opts = {}) {
45
+ return routePatternToRegExp(pattern, opts)?.test(path) ?? false;
46
+ }
47
+
22
48
  // src/edge-config.ts
23
49
  var DEFAULT_EDGE_CONFIG = Object.freeze({
24
50
  version: 1,
@@ -51,6 +77,7 @@ function isEdgeConfig(value) {
51
77
  if (r.sampling !== void 0 && !isSamplingShape(r.sampling)) return false;
52
78
  }
53
79
  if (!isSamplingShape(v.sampling)) return false;
80
+ if (v.minTls !== void 0 && v.minTls !== "1.2" && v.minTls !== "1.3") return false;
54
81
  if (v.queryValueAllowlist !== void 0) {
55
82
  if (!Array.isArray(v.queryValueAllowlist) || v.queryValueAllowlist.length > 16 || !v.queryValueAllowlist.every((e) => typeof e === "string" && e.length > 0 && e.length <= 64)) {
56
83
  return false;
@@ -141,8 +168,7 @@ function matchEdgePathRule(rules, path) {
141
168
  return void 0;
142
169
  }
143
170
  function globToRegExp(pattern) {
144
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
145
- return new RegExp(`^${escaped}$`, "i");
171
+ return routePatternToRegExp(pattern, { caseInsensitive: true }) ?? /$^/;
146
172
  }
147
173
  function _resetEdgeConfigCache() {
148
174
  cache.clear();
@@ -155,6 +181,8 @@ export {
155
181
  fetchEdgeConfig,
156
182
  getEdgeConfig,
157
183
  isEdgeConfig,
158
- matchEdgePathRule
184
+ matchEdgePathRule,
185
+ matchRoutePattern,
186
+ routePatternToRegExp
159
187
  };
160
188
  //# sourceMappingURL=edge-config.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts","../src/http.ts","../src/edge-config.ts"],"sourcesContent":["/**\n * Single source-of-truth for the SDK's\n * package version emitted on verify-access bodies (and any future\n * telemetry). Bumped alongside `package.json#version` on every release.\n *\n * Why a constant rather than `import pkg from '../package.json'`:\n * - `tsconfig.json` sets `rootDir: ./src`; importing the sibling\n * package.json fails the build with \"outside rootDir\".\n * - Build-time string replacement (tsup `define`, esbuild banner, etc.)\n * adds toolchain coupling for a trivial gain.\n * - Embedded readonly constant works in every environment (Node, browser,\n * bundlers, Deno) without runtime fs / network access.\n *\n * Release discipline: a CI lint can grep `package.json#version` against\n * this constant if the two ever diverge in the wild. A manual bump\n * is fine — bumping both in the release-ceremony commit keeps them\n * lockstep.\n */\nexport const SDK_VERSION = '5.14.0';\n","/**\n * Shared outbound-HTTP wrapper — every request the SDK makes to the\n * AstraSync backend (or anywhere else) goes through `sdkFetch` so it\n * carries an identifying User-Agent.\n *\n * Why: Node's global fetch defaults to `user-agent: node`, which is\n * indistinguishable from every other Node client on the wire. Visit\n * Intelligence observed our own SDK/beacon traffic as anonymous \"node\"\n * visits. Self-identification is the platform's own medicine.\n *\n * Browser note: some browsers filter the User-Agent request header\n * silently (never throwing), so this is a no-op there — which is correct:\n * in a browser the page's real UA is the honest identity.\n */\n\nimport { SDK_VERSION } from './version';\n\nexport const SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;\n\n/**\n * Add the SDK User-Agent without changing the SHAPE of the caller's headers\n * (plain records stay plain records) — interceptors, adapters, and tests\n * read `init.headers` as a record, and `Headers` would also lowercase names.\n */\nfunction withUserAgent(initHeaders: HeadersInit | undefined): HeadersInit {\n if (!initHeaders) return { 'user-agent': SDK_USER_AGENT };\n if (initHeaders instanceof Headers) {\n const copy = new Headers(initHeaders);\n if (!copy.has('user-agent')) copy.set('user-agent', SDK_USER_AGENT);\n return copy;\n }\n if (Array.isArray(initHeaders)) {\n const has = initHeaders.some(([name]) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : [...initHeaders, ['user-agent', SDK_USER_AGENT]];\n }\n const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : { 'user-agent': SDK_USER_AGENT, ...initHeaders };\n}\n\nexport const sdkFetch: typeof fetch = (input, init) =>\n fetch(input, { ...init, headers: withUserAgent(init?.headers) });\n","/**\n * Edge verification config — the per-endpoint policy the Trusted Agent\n * Gateway edge adapters (e.g. `@astrasyncai/adapter-lambda`) fetch from the\n * AstraSync dashboard to decide how much verification friction to apply\n * to inbound traffic, and where.\n *\n * Fetch pattern mirrors `well-known.ts`: TTL cache + stale-while-revalidate\n * + in-flight dedupe, with a `_reset*` hook for tests. Fail posture is\n * deliberately asymmetric: this module can NEVER put a site into enforce\n * mode on its own — enforce only ever originates from a successfully\n * fetched config, and a config that has gone stale past `maxStaleMs`\n * degrades back to observe.\n *\n * Which EdgeConfig fields each integration consumes:\n *\n * | Field | Edge adapters | SDK adapters (express/nextjs/mcp) |\n * | ------------------------------ | ------------- | --------------------------------- |\n * | `mode` (observe/enforce) | yes | — |\n * | `depth` | yes | — |\n * | `pathRules` | yes | — |\n * | `sampling.anonymousBeaconRate` | yes | yes — the SDK anonymous beacon |\n * | `failurePosture` | yes | — |\n *\n * The observe/enforce × depth posture is EDGE-ONLY by design: SDK adapters\n * enforce via the dashboard route policy (`fetchRoutes`), not via this\n * config, and consume only the `sampling` block for their anonymous\n * traffic beacon.\n */\n\nimport { sdkFetch } from './http';\nimport type { GatewayConfig } from './types';\n\n/**\n * Edge posture. Canonical Trusted Agent Gateway vocabulary:\n * `observe` ≈ the gateway's `passive` posture (verify +\n * record, enforce nothing), `enforce` ≈ `active`. New value set on a new\n * field name — do NOT reuse `GatewayPosture`'s `active`/`passive` here\n * (different field, different meaning: posture governs an agent-side\n * gateway; mode governs counterparty-side edge interception).\n */\nexport type EdgeMode = 'observe' | 'enforce';\n\n/**\n * How deep the edge checks each request — what is it → who is it → are\n * they allowed:\n * - `classify` — UA / platform-fingerprint classification only; no\n * credential handling.\n * - `authenticate` — establish who is calling: detect and parse agent\n * credentials (X-Astra-*, VI SD-JWT, RFC 9421, ACP,\n * UCP, AP2…) and verify presented AstraSync identities.\n * - `authorize` — full policy decision: credentials cryptographically\n * proven and PDLSS-evaluated via a verify-access round\n * trip. (US spelling on the wire, matching\n * `authorizeSettlement` and HTTP `Authorization`.)\n */\nexport type EdgeVerificationDepth = 'classify' | 'authenticate' | 'authorize';\n\nexport interface EdgePathRule {\n /**\n * `*`-wildcard glob matched against the request path. Matching is\n * case-INsensitive and all regex metacharacters except `*` are\n * escaped. (Deliberately diverges from the express adapter's\n * `matchRoute`, which is historically case-sensitive by default —\n * new surface ships case-insensitive matching as the default.)\n */\n pattern: string;\n /** Pass the request through untouched (static assets); mode/depth ignored. */\n skip?: boolean;\n /** Override the top-level mode for this path. */\n mode?: EdgeMode;\n /** Override the top-level depth for this path. */\n depth?: EdgeVerificationDepth;\n /**\n * Per-path beacon sampling opt-DOWN (VI uplift 1.2). The default is full\n * capture; put a fractional rate on explicitly high-volume/low-value paths\n * (static mirrors, health checks) instead of sampling commerce traffic.\n */\n sampling?: {\n /** Fraction (0..1) of anonymous-bot requests that emit a beacon. */\n anonymousBeaconRate: number;\n };\n}\n\nexport interface EdgeConfig {\n /** Shape version — bump on breaking changes. */\n version: 1;\n /** Default posture for paths no rule matches. */\n mode: EdgeMode;\n /** Default check depth for paths no rule matches. */\n depth: EdgeVerificationDepth;\n /** Ordered, first-match-wins. */\n pathRules: EdgePathRule[];\n sampling: {\n /** Fraction (0..1) of anonymous-bot requests that emit a beacon. */\n anonymousBeaconRate: number;\n };\n /**\n * Query-string parameter names whose VALUES may be captured into\n * `observedMetadata.queryParams` (VI uplift Part 4 — campaign/UTM\n * attribution). Lowercase exact names or a single trailing-`*` glob\n * (`utm_*`); ≤16 entries × ≤64 chars. ABSENT by default — the privacy\n * stance (query values dropped at capture) is unchanged unless the\n * endpoint owner explicitly configures this.\n */\n queryValueAllowlist?: string[];\n /**\n * 4.5.0: the EFFECTIVE failure posture is derived from the effective\n * mode — fail posture follows success posture. Observe fails open\n * (unreachable backend = pass-through, site never breaks); enforce fails\n * closed on verification-infrastructure failure (503\n * `VERIFICATION_UNAVAILABLE`, retry-later — see edge-core/pipeline). This\n * field is retained on the wire for adapter compatibility and as a future\n * explicit override; it is not consulted by the 4.5.x pipeline. Note the\n * distinction from CONFIG staleness: a config stale past 24h still has\n * enforce degraded to observe (`degradeToObserve`) — that is config-trust\n * hygiene, independent of runtime verification availability.\n */\n failurePosture: 'open' | 'closed';\n}\n\n/**\n * The safe default: observe-only, classification depth, nothing enforced.\n * Served by the backend when an endpoint has never been configured, and\n * used by `getEdgeConfig` whenever no config can be fetched.\n *\n * Beacon rate 1.0 (VI uplift 1.2): every anonymous hit beacons by default —\n * a sampled denominator understates anonymous traffic in every dashboard\n * tile and no toggle can reveal the dropped rows. Sampling is a deliberate\n * per-path opt-down (`EdgePathRule.sampling`) for static/high-volume paths.\n */\nexport const DEFAULT_EDGE_CONFIG: EdgeConfig = Object.freeze({\n version: 1,\n mode: 'observe',\n depth: 'classify',\n pathRules: [],\n sampling: Object.freeze({ anonymousBeaconRate: 1.0 }),\n failurePosture: 'open',\n}) as EdgeConfig;\n\nconst EDGE_MODES: readonly string[] = ['observe', 'enforce'];\nconst EDGE_DEPTHS: readonly string[] = ['classify', 'authenticate', 'authorize'];\n\nfunction isSamplingShape(value: unknown): value is { anonymousBeaconRate: number } {\n if (typeof value !== 'object' || value === null) return false;\n const rate = (value as Record<string, unknown>).anonymousBeaconRate;\n return typeof rate === 'number' && rate >= 0 && rate <= 1;\n}\n\n/** Structural guard for configs arriving over the wire. */\nexport function isEdgeConfig(value: unknown): value is EdgeConfig {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n if (v.version !== 1) return false;\n if (!EDGE_MODES.includes(v.mode as string)) return false;\n if (!EDGE_DEPTHS.includes(v.depth as string)) return false;\n if (!Array.isArray(v.pathRules)) return false;\n for (const rule of v.pathRules) {\n if (typeof rule !== 'object' || rule === null) return false;\n const r = rule as Record<string, unknown>;\n if (typeof r.pattern !== 'string' || r.pattern.length === 0) return false;\n if (r.mode !== undefined && !EDGE_MODES.includes(r.mode as string)) return false;\n if (r.depth !== undefined && !EDGE_DEPTHS.includes(r.depth as string)) return false;\n if (r.sampling !== undefined && !isSamplingShape(r.sampling)) return false;\n }\n if (!isSamplingShape(v.sampling)) return false;\n if (v.queryValueAllowlist !== undefined) {\n if (\n !Array.isArray(v.queryValueAllowlist) ||\n v.queryValueAllowlist.length > 16 ||\n !v.queryValueAllowlist.every((e) => typeof e === 'string' && e.length > 0 && e.length <= 64)\n ) {\n return false;\n }\n }\n return v.failurePosture === 'open';\n}\n\nexport interface FetchEdgeConfigSuccess {\n edgeConfig: EdgeConfig;\n etag?: string;\n}\n\nexport type FetchEdgeConfigResult = FetchEdgeConfigSuccess | { notModified: true } | null;\n\n/**\n * Fetch the edge config for an endpoint from the AstraSync backend.\n * `GET {apiBaseUrl}/endpoints/:counterpartyId/edge-config` with the same\n * auth headers as `fetchRoutes`. Pass the cached `etag` to get a cheap\n * `{ notModified: true }` on 304. Returns `null` on any failure —\n * the caller decides how to fall back.\n */\nexport async function fetchEdgeConfig(\n config: Pick<GatewayConfig, 'apiBaseUrl' | 'apiKey'>,\n counterpartyId: string,\n opts?: { etag?: string; timeoutMs?: number }\n): Promise<FetchEdgeConfigResult> {\n if (!counterpartyId) return null;\n const headers: Record<string, string> = { Accept: 'application/json' };\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n headers['X-API-Key'] = config.apiKey;\n }\n if (opts?.etag) headers['If-None-Match'] = opts.etag;\n try {\n const response = await sdkFetch(\n `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,\n { method: 'GET', headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5000) }\n );\n if (response.status === 304) return { notModified: true };\n if (!response.ok) return null;\n const body = (await response.json()) as { data?: { edgeConfig?: unknown } };\n const edgeConfig = body.data?.edgeConfig;\n if (!isEdgeConfig(edgeConfig)) return null;\n return { edgeConfig, etag: response.headers.get('etag') ?? undefined };\n } catch {\n return null;\n }\n}\n\ninterface CacheEntry {\n config: EdgeConfig;\n etag?: string;\n /** When the entry was last confirmed fresh (fetch or 304). */\n lastSuccessAt: number;\n}\n\nconst DEFAULT_TTL_MS = 60 * 1000;\nconst DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1000;\n\nconst cache = new Map<string, CacheEntry>();\nconst inflight = new Map<string, Promise<void>>();\n\nfunction cacheKey(apiBaseUrl: string, counterpartyId: string): string {\n return `${apiBaseUrl}|${counterpartyId}`;\n}\n\n/** Copy of `config` with every enforce (top-level and per-rule) degraded to observe. */\nexport function degradeToObserve(config: EdgeConfig): EdgeConfig {\n if (config.mode !== 'enforce' && !config.pathRules.some((r) => r.mode === 'enforce')) {\n return config;\n }\n return {\n ...config,\n mode: 'observe',\n pathRules: config.pathRules.map((r) => (r.mode === 'enforce' ? { ...r, mode: 'observe' } : r)),\n };\n}\n\nasync function revalidate(\n config: Pick<GatewayConfig, 'apiBaseUrl' | 'apiKey'>,\n counterpartyId: string\n): Promise<void> {\n const key = cacheKey(config.apiBaseUrl, counterpartyId);\n const existing = inflight.get(key);\n if (existing) return existing;\n\n const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache.get(key)?.etag })\n .then((result) => {\n if (result === null) return; // failure — cache untouched, caller falls back\n const now = Date.now();\n if ('notModified' in result) {\n const entry = cache.get(key);\n if (entry) entry.lastSuccessAt = now;\n return;\n }\n cache.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });\n })\n .finally(() => {\n inflight.delete(key);\n });\n\n inflight.set(key, promise);\n return promise;\n}\n\n/**\n * Get the edge config for an endpoint, cached. Never throws, never blocks\n * longer than one fetch.\n *\n * - Fresh cache (within `ttlMs`) → cached config, no network.\n * - Stale cache → cached config immediately + background revalidate\n * (stale-while-revalidate). If the last successful fetch is older than\n * `maxStaleMs` (default 24h), any enforce is degraded to observe — a\n * long-dead backend can never keep a site enforcing.\n * - No cache → await one fetch; on failure return `DEFAULT_EDGE_CONFIG`\n * (observe-only). Defaults never enforce.\n */\nexport async function getEdgeConfig(\n config: Pick<GatewayConfig, 'apiBaseUrl' | 'apiKey'>,\n counterpartyId: string,\n opts?: { ttlMs?: number; maxStaleMs?: number }\n): Promise<EdgeConfig> {\n const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;\n const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;\n const key = cacheKey(config.apiBaseUrl, counterpartyId);\n\n const entry = cache.get(key);\n if (entry) {\n const age = Date.now() - entry.lastSuccessAt;\n if (age > ttlMs) {\n revalidate(config, counterpartyId).catch(() => {\n // background refresh failed — stale entry remains until next call\n });\n }\n return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;\n }\n\n await revalidate(config, counterpartyId).catch(() => {\n // fetch failed — fall through to defaults\n });\n return cache.get(key)?.config ?? DEFAULT_EDGE_CONFIG;\n}\n\n/**\n * First-match-wins path-rule lookup. Case-insensitive `*` globs with all\n * other regex metacharacters escaped (a literal `.` in a pattern matches\n * only `.`). Returns `undefined` when no rule matches — the caller applies\n * the config's top-level mode/depth.\n */\nexport function matchEdgePathRule(rules: EdgePathRule[], path: string): EdgePathRule | undefined {\n for (const rule of rules) {\n if (globToRegExp(rule.pattern).test(path)) return rule;\n }\n return undefined;\n}\n\nfunction globToRegExp(pattern: string): RegExp {\n const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '.*');\n return new RegExp(`^${escaped}$`, 'i');\n}\n\n/** Reset cache — for testing only. */\nexport function _resetEdgeConfigCache(): void {\n cache.clear();\n inflight.clear();\n}\n"],"mappings":"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","names":["has"]}
1
+ {"version":3,"sources":["../src/version.ts","../src/http.ts","../src/route-pattern.ts","../src/edge-config.ts"],"sourcesContent":["/**\n * Single source-of-truth for the SDK's\n * package version emitted on verify-access bodies (and any future\n * telemetry). Bumped alongside `package.json#version` on every release.\n *\n * Why a constant rather than `import pkg from '../package.json'`:\n * - `tsconfig.json` sets `rootDir: ./src`; importing the sibling\n * package.json fails the build with \"outside rootDir\".\n * - Build-time string replacement (tsup `define`, esbuild banner, etc.)\n * adds toolchain coupling for a trivial gain.\n * - Embedded readonly constant works in every environment (Node, browser,\n * bundlers, Deno) without runtime fs / network access.\n *\n * Release discipline: a CI lint can grep `package.json#version` against\n * this constant if the two ever diverge in the wild. A manual bump\n * is fine — bumping both in the release-ceremony commit keeps them\n * lockstep.\n */\nexport const SDK_VERSION = '5.15.0';\n","/**\n * Shared outbound-HTTP wrapper — every request the SDK makes to the\n * AstraSync backend (or anywhere else) goes through `sdkFetch` so it\n * carries an identifying User-Agent.\n *\n * Why: Node's global fetch defaults to `user-agent: node`, which is\n * indistinguishable from every other Node client on the wire. Visit\n * Intelligence observed our own SDK/beacon traffic as anonymous \"node\"\n * visits. Self-identification is the platform's own medicine.\n *\n * Browser note: some browsers filter the User-Agent request header\n * silently (never throwing), so this is a no-op there — which is correct:\n * in a browser the page's real UA is the honest identity.\n */\n\nimport { SDK_VERSION } from './version';\n\nexport const SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;\n\n/**\n * Add the SDK User-Agent without changing the SHAPE of the caller's headers\n * (plain records stay plain records) — interceptors, adapters, and tests\n * read `init.headers` as a record, and `Headers` would also lowercase names.\n */\nfunction withUserAgent(initHeaders: HeadersInit | undefined): HeadersInit {\n if (!initHeaders) return { 'user-agent': SDK_USER_AGENT };\n if (initHeaders instanceof Headers) {\n const copy = new Headers(initHeaders);\n if (!copy.has('user-agent')) copy.set('user-agent', SDK_USER_AGENT);\n return copy;\n }\n if (Array.isArray(initHeaders)) {\n const has = initHeaders.some(([name]) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : [...initHeaders, ['user-agent', SDK_USER_AGENT]];\n }\n const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : { 'user-agent': SDK_USER_AGENT, ...initHeaders };\n}\n\nexport const sdkFetch: typeof fetch = (input, init) =>\n fetch(input, { ...init, headers: withUserAgent(init?.headers) });\n","/**\n * Route pattern compiler shared by every matcher of endpoint route policy:\n * the express and Next.js middleware, the edge config and the AstraSync\n * bridge (round 27.9 — they each had their own copy, and none understood\n * path parameters).\n *\n * Pattern syntax:\n * - `*` any characters, including `/` (unchanged since v1)\n * - `:name` exactly one path segment (no `/`), e.g.\n * `/ucp/v1/checkout-sessions/:id/complete`\n * - anything else is literal — `.`, `?`, `+`, `(`… are escaped (they used\n * to be live regex syntax, so `/robots.txt` also matched `/robotsXtxt`).\n * Anchored `^…$`. Case-sensitive unless `caseInsensitive` is set.\n */\nexport function routePatternToRegExp(\n pattern: string,\n opts: { caseInsensitive?: boolean } = {}\n): RegExp | null {\n let source = '';\n for (let i = 0; i < pattern.length; i++) {\n const ch = pattern[i];\n if (ch === '*') {\n source += '.*';\n } else if (\n ch === ':' &&\n /[A-Za-z_]/.test(pattern[i + 1] ?? '') &&\n (i === 0 || pattern[i - 1] === '/')\n ) {\n // `:name` at the start of a segment: consume the identifier.\n let j = i + 1;\n while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) j++;\n source += '[^/]+';\n i = j - 1;\n } else {\n source += ch.replace(/[.+?^${}()|[\\]\\\\/]/g, '\\\\$&');\n }\n }\n try {\n return new RegExp(`^${source}$`, opts.caseInsensitive ? 'i' : '');\n } catch {\n // A malformed stored pattern never matches (fail closed at the caller).\n return null;\n }\n}\n\n/** True when `path` matches `pattern` (see routePatternToRegExp). */\nexport function matchRoutePattern(\n pattern: string,\n path: string,\n opts: { caseInsensitive?: boolean } = {}\n): boolean {\n return routePatternToRegExp(pattern, opts)?.test(path) ?? false;\n}\n","/**\n * Edge verification config — the per-endpoint policy the Trusted Agent\n * Gateway edge adapters (e.g. `@astrasyncai/adapter-lambda`) fetch from the\n * AstraSync dashboard to decide how much verification friction to apply\n * to inbound traffic, and where.\n *\n * Fetch pattern mirrors `well-known.ts`: TTL cache + stale-while-revalidate\n * + in-flight dedupe, with a `_reset*` hook for tests. Fail posture is\n * deliberately asymmetric: this module can NEVER put a site into enforce\n * mode on its own — enforce only ever originates from a successfully\n * fetched config, and a config that has gone stale past `maxStaleMs`\n * degrades back to observe.\n *\n * Which EdgeConfig fields each integration consumes:\n *\n * | Field | Edge adapters | SDK adapters (express/nextjs/mcp) |\n * | ------------------------------ | ------------- | --------------------------------- |\n * | `mode` (observe/enforce) | yes | — |\n * | `depth` | yes | — |\n * | `pathRules` | yes | — |\n * | `sampling.anonymousBeaconRate` | yes | yes — the SDK anonymous beacon |\n * | `failurePosture` | yes | — |\n *\n * The observe/enforce × depth posture is EDGE-ONLY by design: SDK adapters\n * enforce via the dashboard route policy (`fetchRoutes`), not via this\n * config, and consume only the `sampling` block for their anonymous\n * traffic beacon.\n */\n\nimport { sdkFetch } from './http';\nimport type { GatewayConfig } from './types';\nimport { routePatternToRegExp } from './route-pattern';\n\n/**\n * Edge posture. Canonical Trusted Agent Gateway vocabulary:\n * `observe` ≈ the gateway's `passive` posture (verify +\n * record, enforce nothing), `enforce` ≈ `active`. New value set on a new\n * field name — do NOT reuse `GatewayPosture`'s `active`/`passive` here\n * (different field, different meaning: posture governs an agent-side\n * gateway; mode governs counterparty-side edge interception).\n */\nexport type EdgeMode = 'observe' | 'enforce';\n\n/**\n * How deep the edge checks each request — what is it → who is it → are\n * they allowed:\n * - `classify` — UA / platform-fingerprint classification only; no\n * credential handling.\n * - `authenticate` — establish who is calling: detect and parse agent\n * credentials (X-Astra-*, VI SD-JWT, RFC 9421, ACP,\n * UCP, AP2…) and verify presented AstraSync identities.\n * - `authorize` — full policy decision: credentials cryptographically\n * proven and PDLSS-evaluated via a verify-access round\n * trip. (US spelling on the wire, matching\n * `authorizeSettlement` and HTTP `Authorization`.)\n */\nexport type EdgeVerificationDepth = 'classify' | 'authenticate' | 'authorize';\n\nexport interface EdgePathRule {\n /**\n * `*`-wildcard glob matched against the request path. Matching is\n * case-INsensitive and all regex metacharacters except `*` are\n * escaped. (Deliberately diverges from the express adapter's\n * `matchRoute`, which is historically case-sensitive by default —\n * new surface ships case-insensitive matching as the default.)\n */\n pattern: string;\n /** Pass the request through untouched (static assets); mode/depth ignored. */\n skip?: boolean;\n /** Override the top-level mode for this path. */\n mode?: EdgeMode;\n /** Override the top-level depth for this path. */\n depth?: EdgeVerificationDepth;\n /**\n * Per-path beacon sampling opt-DOWN (VI uplift 1.2). The default is full\n * capture; put a fractional rate on explicitly high-volume/low-value paths\n * (static mirrors, health checks) instead of sampling commerce traffic.\n */\n sampling?: {\n /** Fraction (0..1) of anonymous-bot requests that emit a beacon. */\n anonymousBeaconRate: number;\n };\n}\n\nexport interface EdgeConfig {\n /** Shape version — bump on breaking changes. */\n version: 1;\n /** Default posture for paths no rule matches. */\n mode: EdgeMode;\n /** Default check depth for paths no rule matches. */\n depth: EdgeVerificationDepth;\n /** Ordered, first-match-wins. */\n pathRules: EdgePathRule[];\n sampling: {\n /** Fraction (0..1) of anonymous-bot requests that emit a beacon. */\n anonymousBeaconRate: number;\n };\n /**\n * Query-string parameter names whose VALUES may be captured into\n * `observedMetadata.queryParams` (VI uplift Part 4 — campaign/UTM\n * attribution). Lowercase exact names or a single trailing-`*` glob\n * (`utm_*`); ≤16 entries × ≤64 chars. ABSENT by default — the privacy\n * stance (query values dropped at capture) is unchanged unless the\n * endpoint owner explicitly configures this.\n */\n queryValueAllowlist?: string[];\n /**\n * 5.15.0: minimum TLS version for AGENT traffic (UCP 2026-08-25 requires\n * TLS 1.3). Read from CloudFront-Viewer-TLS; follows the path's mode —\n * enforce answers 426 Upgrade Required, observe only logs. Human browsers\n * are never gated, and an unknown version never blocks. ABSENT = no check.\n */\n minTls?: '1.2' | '1.3';\n /**\n * 4.5.0: the EFFECTIVE failure posture is derived from the effective\n * mode — fail posture follows success posture. Observe fails open\n * (unreachable backend = pass-through, site never breaks); enforce fails\n * closed on verification-infrastructure failure (503\n * `VERIFICATION_UNAVAILABLE`, retry-later — see edge-core/pipeline). This\n * field is retained on the wire for adapter compatibility and as a future\n * explicit override; it is not consulted by the 4.5.x pipeline. Note the\n * distinction from CONFIG staleness: a config stale past 24h still has\n * enforce degraded to observe (`degradeToObserve`) — that is config-trust\n * hygiene, independent of runtime verification availability.\n */\n failurePosture: 'open' | 'closed';\n}\n\n/**\n * The safe default: observe-only, classification depth, nothing enforced.\n * Served by the backend when an endpoint has never been configured, and\n * used by `getEdgeConfig` whenever no config can be fetched.\n *\n * Beacon rate 1.0 (VI uplift 1.2): every anonymous hit beacons by default —\n * a sampled denominator understates anonymous traffic in every dashboard\n * tile and no toggle can reveal the dropped rows. Sampling is a deliberate\n * per-path opt-down (`EdgePathRule.sampling`) for static/high-volume paths.\n */\nexport const DEFAULT_EDGE_CONFIG: EdgeConfig = Object.freeze({\n version: 1,\n mode: 'observe',\n depth: 'classify',\n pathRules: [],\n sampling: Object.freeze({ anonymousBeaconRate: 1.0 }),\n failurePosture: 'open',\n}) as EdgeConfig;\n\nconst EDGE_MODES: readonly string[] = ['observe', 'enforce'];\nconst EDGE_DEPTHS: readonly string[] = ['classify', 'authenticate', 'authorize'];\n\nfunction isSamplingShape(value: unknown): value is { anonymousBeaconRate: number } {\n if (typeof value !== 'object' || value === null) return false;\n const rate = (value as Record<string, unknown>).anonymousBeaconRate;\n return typeof rate === 'number' && rate >= 0 && rate <= 1;\n}\n\n/** Structural guard for configs arriving over the wire. */\nexport function isEdgeConfig(value: unknown): value is EdgeConfig {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n if (v.version !== 1) return false;\n if (!EDGE_MODES.includes(v.mode as string)) return false;\n if (!EDGE_DEPTHS.includes(v.depth as string)) return false;\n if (!Array.isArray(v.pathRules)) return false;\n for (const rule of v.pathRules) {\n if (typeof rule !== 'object' || rule === null) return false;\n const r = rule as Record<string, unknown>;\n if (typeof r.pattern !== 'string' || r.pattern.length === 0) return false;\n if (r.mode !== undefined && !EDGE_MODES.includes(r.mode as string)) return false;\n if (r.depth !== undefined && !EDGE_DEPTHS.includes(r.depth as string)) return false;\n if (r.sampling !== undefined && !isSamplingShape(r.sampling)) return false;\n }\n if (!isSamplingShape(v.sampling)) return false;\n if (v.minTls !== undefined && v.minTls !== '1.2' && v.minTls !== '1.3') return false;\n if (v.queryValueAllowlist !== undefined) {\n if (\n !Array.isArray(v.queryValueAllowlist) ||\n v.queryValueAllowlist.length > 16 ||\n !v.queryValueAllowlist.every((e) => typeof e === 'string' && e.length > 0 && e.length <= 64)\n ) {\n return false;\n }\n }\n return v.failurePosture === 'open';\n}\n\nexport interface FetchEdgeConfigSuccess {\n edgeConfig: EdgeConfig;\n etag?: string;\n}\n\nexport type FetchEdgeConfigResult = FetchEdgeConfigSuccess | { notModified: true } | null;\n\n/**\n * Fetch the edge config for an endpoint from the AstraSync backend.\n * `GET {apiBaseUrl}/endpoints/:counterpartyId/edge-config` with the same\n * auth headers as `fetchRoutes`. Pass the cached `etag` to get a cheap\n * `{ notModified: true }` on 304. Returns `null` on any failure —\n * the caller decides how to fall back.\n */\nexport async function fetchEdgeConfig(\n config: Pick<GatewayConfig, 'apiBaseUrl' | 'apiKey'>,\n counterpartyId: string,\n opts?: { etag?: string; timeoutMs?: number }\n): Promise<FetchEdgeConfigResult> {\n if (!counterpartyId) return null;\n const headers: Record<string, string> = { Accept: 'application/json' };\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n headers['X-API-Key'] = config.apiKey;\n }\n if (opts?.etag) headers['If-None-Match'] = opts.etag;\n try {\n const response = await sdkFetch(\n `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,\n { method: 'GET', headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5000) }\n );\n if (response.status === 304) return { notModified: true };\n if (!response.ok) return null;\n const body = (await response.json()) as { data?: { edgeConfig?: unknown } };\n const edgeConfig = body.data?.edgeConfig;\n if (!isEdgeConfig(edgeConfig)) return null;\n return { edgeConfig, etag: response.headers.get('etag') ?? undefined };\n } catch {\n return null;\n }\n}\n\ninterface CacheEntry {\n config: EdgeConfig;\n etag?: string;\n /** When the entry was last confirmed fresh (fetch or 304). */\n lastSuccessAt: number;\n}\n\nconst DEFAULT_TTL_MS = 60 * 1000;\nconst DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1000;\n\nconst cache = new Map<string, CacheEntry>();\nconst inflight = new Map<string, Promise<void>>();\n\nfunction cacheKey(apiBaseUrl: string, counterpartyId: string): string {\n return `${apiBaseUrl}|${counterpartyId}`;\n}\n\n/** Copy of `config` with every enforce (top-level and per-rule) degraded to observe. */\nexport function degradeToObserve(config: EdgeConfig): EdgeConfig {\n if (config.mode !== 'enforce' && !config.pathRules.some((r) => r.mode === 'enforce')) {\n return config;\n }\n return {\n ...config,\n mode: 'observe',\n pathRules: config.pathRules.map((r) => (r.mode === 'enforce' ? { ...r, mode: 'observe' } : r)),\n };\n}\n\nasync function revalidate(\n config: Pick<GatewayConfig, 'apiBaseUrl' | 'apiKey'>,\n counterpartyId: string\n): Promise<void> {\n const key = cacheKey(config.apiBaseUrl, counterpartyId);\n const existing = inflight.get(key);\n if (existing) return existing;\n\n const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache.get(key)?.etag })\n .then((result) => {\n if (result === null) return; // failure — cache untouched, caller falls back\n const now = Date.now();\n if ('notModified' in result) {\n const entry = cache.get(key);\n if (entry) entry.lastSuccessAt = now;\n return;\n }\n cache.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });\n })\n .finally(() => {\n inflight.delete(key);\n });\n\n inflight.set(key, promise);\n return promise;\n}\n\n/**\n * Get the edge config for an endpoint, cached. Never throws, never blocks\n * longer than one fetch.\n *\n * - Fresh cache (within `ttlMs`) → cached config, no network.\n * - Stale cache → cached config immediately + background revalidate\n * (stale-while-revalidate). If the last successful fetch is older than\n * `maxStaleMs` (default 24h), any enforce is degraded to observe — a\n * long-dead backend can never keep a site enforcing.\n * - No cache → await one fetch; on failure return `DEFAULT_EDGE_CONFIG`\n * (observe-only). Defaults never enforce.\n */\nexport async function getEdgeConfig(\n config: Pick<GatewayConfig, 'apiBaseUrl' | 'apiKey'>,\n counterpartyId: string,\n opts?: { ttlMs?: number; maxStaleMs?: number }\n): Promise<EdgeConfig> {\n const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;\n const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;\n const key = cacheKey(config.apiBaseUrl, counterpartyId);\n\n const entry = cache.get(key);\n if (entry) {\n const age = Date.now() - entry.lastSuccessAt;\n if (age > ttlMs) {\n revalidate(config, counterpartyId).catch(() => {\n // background refresh failed — stale entry remains until next call\n });\n }\n return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;\n }\n\n await revalidate(config, counterpartyId).catch(() => {\n // fetch failed — fall through to defaults\n });\n return cache.get(key)?.config ?? DEFAULT_EDGE_CONFIG;\n}\n\n/**\n * First-match-wins path-rule lookup. Case-insensitive `*` globs with all\n * other regex metacharacters escaped (a literal `.` in a pattern matches\n * only `.`). Returns `undefined` when no rule matches — the caller applies\n * the config's top-level mode/depth.\n */\nexport function matchEdgePathRule(rules: EdgePathRule[], path: string): EdgePathRule | undefined {\n for (const rule of rules) {\n if (globToRegExp(rule.pattern).test(path)) return rule;\n }\n return undefined;\n}\n\nfunction globToRegExp(pattern: string): RegExp {\n // 27.9: shared compiler (route-pattern.ts); the edge matches case-insensitively.\n return routePatternToRegExp(pattern, { caseInsensitive: true }) ?? /$^/;\n}\n\n/** Reset cache — for testing only. */\nexport function _resetEdgeConfigCache(): void {\n cache.clear();\n inflight.clear();\n}\n\n// 27.9: the shared route pattern compiler, re-exported on this subpath so\n// consumers (the AstraSync bridge) can import it without the package root.\nexport { routePatternToRegExp, matchRoutePattern } from './route-pattern';\n"],"mappings":"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","names":["has"]}
@@ -1,6 +1,6 @@
1
1
  import { EdgeMode, EdgeVerificationDepth, EdgeConfig } from '../edge-config.mjs';
2
2
  import { PlatformFingerprint } from '../platform-signatures.mjs';
3
- import { C as CommerceArtifactsPayload, V as VerificationRequest } from '../types-DlshIIR-.mjs';
3
+ import { C as CommerceArtifactsPayload, V as VerificationRequest } from '../types-Eg6VoBNH.mjs';
4
4
  import { verify } from '../verify.mjs';
5
5
  import { ObservedMetadata } from '../metadata-capture.mjs';
6
6
 
@@ -140,6 +140,7 @@ interface TierOutcome {
140
140
  };
141
141
  stepUp?: {
142
142
  pollUrl?: string;
143
+ approvalUrl?: string;
143
144
  expiresAt?: string;
144
145
  };
145
146
  correlationId?: string;
@@ -306,6 +307,13 @@ interface EdgeEvaluation {
306
307
  * response).
307
308
  */
308
309
  declare function evaluateEdgeRequest(options: EdgeCoreOptions, req: NormalizedRequest, config: EdgeConfig, apiKey: string | undefined): Promise<EdgeEvaluation>;
310
+ /** Negotiated TLS version from CloudFront-Viewer-TLS (`TLSv1.3:cipher:…`). */
311
+ declare function negotiatedTlsVersion(req: NormalizedRequest): string | null;
312
+ /** The violation when the request's TLS is below `minTls`; null = fine or unknown. */
313
+ declare function checkMinTls(req: NormalizedRequest, minTls: EdgeConfig['minTls']): {
314
+ minimum: string;
315
+ negotiated: string;
316
+ } | null;
309
317
 
310
318
  /**
311
319
  * Decision → abstract outcome-application wiring. Executed in BOTH modes
@@ -538,6 +546,7 @@ interface ManualReviewBody {
538
546
  message: string;
539
547
  stepUpApproval?: {
540
548
  pollUrl?: string;
549
+ approvalUrl?: string;
541
550
  expiresAt?: string;
542
551
  };
543
552
  correlationId?: string;
@@ -604,4 +613,4 @@ declare function requestUrl(req: NormalizedRequest, host: string): string;
604
613
  */
605
614
  declare function buildObservedMetadata(req: NormalizedRequest, queryValueAllowlist?: string[]): ObservedMetadata;
606
615
 
607
- export { ASAT_HEADERS, type BeaconBatchMeta, BeaconBuffer, type BeaconBufferOptions, type BeaconDestination, type BeaconItem, type Classification, type DenyBody, type DetectedCommerce, type EdgeCoreOptions, type EdgeDecision, type EdgeEvaluation, type EdgeLogger, type EffectivePolicy, type ManualReviewBody, type NormalizedRequest, type OutcomeApplication, type PlatformIo, type TierOutcome, type TrafficTier, _patterns, applyOutcome, buildDenyBody, buildManualReviewBody, buildObservedMetadata, buildVerificationRequest, classify, detectCommerce, emitBeacon, evaluateEdgeRequest, getBeaconBuffer, headerValue, outcomeFromVerifyResult, passUntouched, requestUrl, resetBeaconBuffer, resolvePolicy, runTier, sampled, withBudget };
616
+ export { ASAT_HEADERS, type BeaconBatchMeta, BeaconBuffer, type BeaconBufferOptions, type BeaconDestination, type BeaconItem, type Classification, type DenyBody, type DetectedCommerce, type EdgeCoreOptions, type EdgeDecision, type EdgeEvaluation, type EdgeLogger, type EffectivePolicy, type ManualReviewBody, type NormalizedRequest, type OutcomeApplication, type PlatformIo, type TierOutcome, type TrafficTier, _patterns, applyOutcome, buildDenyBody, buildManualReviewBody, buildObservedMetadata, buildVerificationRequest, checkMinTls, classify, detectCommerce, emitBeacon, evaluateEdgeRequest, getBeaconBuffer, headerValue, negotiatedTlsVersion, outcomeFromVerifyResult, passUntouched, requestUrl, resetBeaconBuffer, resolvePolicy, runTier, sampled, withBudget };
@@ -1,6 +1,6 @@
1
1
  import { EdgeMode, EdgeVerificationDepth, EdgeConfig } from '../edge-config.js';
2
2
  import { PlatformFingerprint } from '../platform-signatures.js';
3
- import { C as CommerceArtifactsPayload, V as VerificationRequest } from '../types-C_b4QTyO.js';
3
+ import { C as CommerceArtifactsPayload, V as VerificationRequest } from '../types-DtJjlW4O.js';
4
4
  import { verify } from '../verify.js';
5
5
  import { ObservedMetadata } from '../metadata-capture.js';
6
6
 
@@ -140,6 +140,7 @@ interface TierOutcome {
140
140
  };
141
141
  stepUp?: {
142
142
  pollUrl?: string;
143
+ approvalUrl?: string;
143
144
  expiresAt?: string;
144
145
  };
145
146
  correlationId?: string;
@@ -306,6 +307,13 @@ interface EdgeEvaluation {
306
307
  * response).
307
308
  */
308
309
  declare function evaluateEdgeRequest(options: EdgeCoreOptions, req: NormalizedRequest, config: EdgeConfig, apiKey: string | undefined): Promise<EdgeEvaluation>;
310
+ /** Negotiated TLS version from CloudFront-Viewer-TLS (`TLSv1.3:cipher:…`). */
311
+ declare function negotiatedTlsVersion(req: NormalizedRequest): string | null;
312
+ /** The violation when the request's TLS is below `minTls`; null = fine or unknown. */
313
+ declare function checkMinTls(req: NormalizedRequest, minTls: EdgeConfig['minTls']): {
314
+ minimum: string;
315
+ negotiated: string;
316
+ } | null;
309
317
 
310
318
  /**
311
319
  * Decision → abstract outcome-application wiring. Executed in BOTH modes
@@ -538,6 +546,7 @@ interface ManualReviewBody {
538
546
  message: string;
539
547
  stepUpApproval?: {
540
548
  pollUrl?: string;
549
+ approvalUrl?: string;
541
550
  expiresAt?: string;
542
551
  };
543
552
  correlationId?: string;
@@ -604,4 +613,4 @@ declare function requestUrl(req: NormalizedRequest, host: string): string;
604
613
  */
605
614
  declare function buildObservedMetadata(req: NormalizedRequest, queryValueAllowlist?: string[]): ObservedMetadata;
606
615
 
607
- export { ASAT_HEADERS, type BeaconBatchMeta, BeaconBuffer, type BeaconBufferOptions, type BeaconDestination, type BeaconItem, type Classification, type DenyBody, type DetectedCommerce, type EdgeCoreOptions, type EdgeDecision, type EdgeEvaluation, type EdgeLogger, type EffectivePolicy, type ManualReviewBody, type NormalizedRequest, type OutcomeApplication, type PlatformIo, type TierOutcome, type TrafficTier, _patterns, applyOutcome, buildDenyBody, buildManualReviewBody, buildObservedMetadata, buildVerificationRequest, classify, detectCommerce, emitBeacon, evaluateEdgeRequest, getBeaconBuffer, headerValue, outcomeFromVerifyResult, passUntouched, requestUrl, resetBeaconBuffer, resolvePolicy, runTier, sampled, withBudget };
616
+ export { ASAT_HEADERS, type BeaconBatchMeta, BeaconBuffer, type BeaconBufferOptions, type BeaconDestination, type BeaconItem, type Classification, type DenyBody, type DetectedCommerce, type EdgeCoreOptions, type EdgeDecision, type EdgeEvaluation, type EdgeLogger, type EffectivePolicy, type ManualReviewBody, type NormalizedRequest, type OutcomeApplication, type PlatformIo, type TierOutcome, type TrafficTier, _patterns, applyOutcome, buildDenyBody, buildManualReviewBody, buildObservedMetadata, buildVerificationRequest, checkMinTls, classify, detectCommerce, emitBeacon, evaluateEdgeRequest, getBeaconBuffer, headerValue, negotiatedTlsVersion, outcomeFromVerifyResult, passUntouched, requestUrl, resetBeaconBuffer, resolvePolicy, runTier, sampled, withBudget };