@astrasyncai/verification-gateway 4.6.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter-interface/interface.d.mts +2 -2
- package/dist/adapter-interface/interface.d.ts +2 -2
- package/dist/adapters/express.d.mts +2 -2
- package/dist/adapters/express.d.ts +2 -2
- package/dist/adapters/express.js +25 -35
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +25 -35
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +2 -2
- package/dist/adapters/mcp.d.ts +2 -2
- package/dist/adapters/mcp.js +42 -67
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +41 -66
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +2 -2
- package/dist/adapters/nextjs.d.ts +2 -2
- package/dist/adapters/nextjs.js +23 -32
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +23 -32
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +2 -2
- package/dist/adapters/sdk.d.ts +2 -2
- package/dist/adapters/sdk.js +22 -126
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +22 -124
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.d.mts +2 -2
- package/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js +1 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +1 -1
- package/dist/agent/index.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +23 -31
- package/dist/bin/astrasync-codex-hook.js +23 -31
- package/dist/bin/astrasync-guard.js +23 -31
- package/dist/bin/astrasync.js +28 -36
- package/dist/browser/background.js +23 -31
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +23 -31
- package/dist/browser/background.mjs.map +1 -1
- package/dist/browser/browser-adapter.d.mts +2 -2
- package/dist/browser/browser-adapter.d.ts +2 -2
- package/dist/claude-code/claude-code-adapter.d.mts +2 -2
- package/dist/claude-code/claude-code-adapter.d.ts +2 -2
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/cli/index.mjs.map +1 -1
- package/dist/codex/index.d.mts +2 -2
- package/dist/codex/index.d.ts +2 -2
- package/dist/codex/index.js +23 -31
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +23 -31
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/cursor-adapter.d.mts +2 -2
- package/dist/cursor/cursor-adapter.d.ts +2 -2
- package/dist/cursor/extension.d.mts +2 -2
- package/dist/cursor/extension.d.ts +2 -2
- package/dist/cursor/extension.js +23 -31
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +23 -31
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +1 -1
- package/dist/edge-config.d.ts +1 -1
- package/dist/edge-config.js +1 -1
- package/dist/edge-config.js.map +1 -1
- package/dist/edge-config.mjs +1 -1
- package/dist/edge-config.mjs.map +1 -1
- package/dist/edge-core/index.d.mts +12 -3
- package/dist/edge-core/index.d.ts +12 -3
- package/dist/edge-core/index.js +42 -36
- package/dist/edge-core/index.js.map +1 -1
- package/dist/edge-core/index.mjs +42 -36
- package/dist/edge-core/index.mjs.map +1 -1
- package/dist/{express-DBtPKXOL.d.mts → express-KkBZBMqi.d.mts} +1 -1
- package/dist/{express-D-8Ks2Uo.d.ts → express-xK8pRrmj.d.ts} +1 -1
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +23 -31
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +23 -31
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +2 -2
- package/dist/git-trigger/git-hooks.d.ts +2 -2
- package/dist/{index-kGCGWd5x.d.mts → index-DIRzR5IS.d.mts} +1 -1
- package/dist/{index-DlZORQsb.d.ts → index-DLQ4Rcuc.d.ts} +1 -1
- package/dist/{index-CUfmJ1Dq.d.mts → index-GPDn_EGI.d.mts} +1 -1
- package/dist/{index-BND3kQev.d.ts → index-Hnz5VyMR.d.ts} +1 -1
- package/dist/index.d.mts +14 -10
- package/dist/index.d.ts +14 -10
- package/dist/index.js +44 -219
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +44 -212
- package/dist/index.mjs.map +1 -1
- package/dist/local-evaluator/evaluator.d.mts +2 -2
- package/dist/local-evaluator/evaluator.d.ts +2 -2
- package/dist/local-evaluator/evaluator.js.map +1 -1
- package/dist/local-evaluator/evaluator.mjs.map +1 -1
- package/dist/{mcp-DToTlwIn.d.ts → mcp-DPpu4xQz.d.ts} +53 -52
- package/dist/{mcp-ZRN4kY3b.d.mts → mcp-Dju_Sr1o.d.mts} +53 -52
- package/dist/{nextjs-C0KASOy_.d.mts → nextjs-BuoDBN4g.d.mts} +1 -1
- package/dist/{nextjs-lwNvWYWT.d.ts → nextjs-rZKzY_8o.d.ts} +1 -1
- package/dist/registration/index.js +1 -1
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs +1 -1
- package/dist/registration/index.mjs.map +1 -1
- package/dist/sdk-CdO6TIEf.d.mts +142 -0
- package/dist/sdk-DxHuIJBm.d.ts +142 -0
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js +1 -1
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +1 -1
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-DhhJxKIC.d.ts → types-3UV7IZx7.d.ts} +1 -3
- package/dist/{types-CGPU7HLi.d.ts → types-CNBaZKxY.d.ts} +24 -50
- package/dist/{types-Cx9hTpKI.d.mts → types-CSHcByFX.d.mts} +24 -50
- package/dist/{types-CfSJLS4i.d.mts → types-CoDjpbVY.d.mts} +1 -3
- package/dist/ui/index.d.mts +2 -17
- package/dist/ui/index.d.ts +2 -17
- package/dist/ui/index.js +1 -43
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/index.mjs +1 -42
- package/dist/ui/index.mjs.map +1 -1
- package/dist/verify.d.mts +3 -4
- package/dist/verify.d.ts +3 -4
- package/dist/verify.js +22 -30
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +22 -30
- package/dist/verify.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/sdk-B4aodEwj.d.ts +0 -215
- package/dist/sdk-B9dSwum7.d.mts +0 -215
package/dist/edge-config.d.mts
CHANGED
package/dist/edge-config.d.ts
CHANGED
package/dist/edge-config.js
CHANGED
package/dist/edge-config.js.map
CHANGED
|
@@ -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\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 * 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 */\nexport const DEFAULT_EDGE_CONFIG: EdgeConfig = Object.freeze({\n version: 1,\n mode: 'observe',\n depth: 'classify',\n pathRules: [],\n sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),\n failurePosture: 'open',\n}) as EdgeConfig;\n\nconst EDGE_MODES: readonly string[] = ['observe', 'enforce'];\nconst EDGE_DEPTHS: readonly string[] = ['classify', 'authenticate', 'authorize'];\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 }\n const sampling = v.sampling as Record<string, unknown> | undefined;\n if (\n typeof sampling !== 'object' ||\n sampling === null ||\n typeof sampling.anonymousBeaconRate !== 'number' ||\n sampling.anonymousBeaconRate < 0 ||\n sampling.anonymousBeaconRate > 1\n ) {\n return false;\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 = '4.6.0';\n","/**\n * Shared outbound-HTTP wrapper — every request the SDK makes to the\n * AstraSync backend (or anywhere else) goes through `sdkFetch` so it\n * carries an identifying User-Agent.\n *\n * Why: Node's global fetch defaults to `user-agent: node`, which is\n * indistinguishable from every other Node client on the wire. Visit\n * Intelligence observed our own SDK/beacon traffic as anonymous \"node\"\n * visits. Self-identification is the platform's own medicine.\n *\n * Browser note: some browsers filter the User-Agent request header\n * silently (never throwing), so this is a no-op there — which is correct:\n * in a browser the page's real UA is the honest identity.\n */\n\nimport { SDK_VERSION } from './version';\n\nexport const SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;\n\n/**\n * Add the SDK User-Agent without changing the SHAPE of the caller's headers\n * (plain records stay plain records) — interceptors, adapters, and tests\n * read `init.headers` as a record, and `Headers` would also lowercase names.\n */\nfunction withUserAgent(initHeaders: HeadersInit | undefined): HeadersInit {\n if (!initHeaders) return { 'user-agent': SDK_USER_AGENT };\n if (initHeaders instanceof Headers) {\n const copy = new Headers(initHeaders);\n if (!copy.has('user-agent')) copy.set('user-agent', SDK_USER_AGENT);\n return copy;\n }\n if (Array.isArray(initHeaders)) {\n const has = initHeaders.some(([name]) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : [...initHeaders, ['user-agent', SDK_USER_AGENT]];\n }\n const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : { 'user-agent': SDK_USER_AGENT, ...initHeaders };\n}\n\nexport const sdkFetch: typeof fetch = (input, init) =>\n fetch(input, { ...init, headers: withUserAgent(init?.headers) });\n"],"mappings":"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","names":["has"]}
|
|
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\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 * 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 */\nexport const DEFAULT_EDGE_CONFIG: EdgeConfig = Object.freeze({\n version: 1,\n mode: 'observe',\n depth: 'classify',\n pathRules: [],\n sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),\n failurePosture: 'open',\n}) as EdgeConfig;\n\nconst EDGE_MODES: readonly string[] = ['observe', 'enforce'];\nconst EDGE_DEPTHS: readonly string[] = ['classify', 'authenticate', 'authorize'];\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 }\n const sampling = v.sampling as Record<string, unknown> | undefined;\n if (\n typeof sampling !== 'object' ||\n sampling === null ||\n typeof sampling.anonymousBeaconRate !== 'number' ||\n sampling.anonymousBeaconRate < 0 ||\n sampling.anonymousBeaconRate > 1\n ) {\n return false;\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.0.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"]}
|
package/dist/edge-config.mjs
CHANGED
package/dist/edge-config.mjs.map
CHANGED
|
@@ -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 = '4.6.0';\n","/**\n * Shared outbound-HTTP wrapper — every request the SDK makes to the\n * AstraSync backend (or anywhere else) goes through `sdkFetch` so it\n * carries an identifying User-Agent.\n *\n * Why: Node's global fetch defaults to `user-agent: node`, which is\n * indistinguishable from every other Node client on the wire. Visit\n * Intelligence observed our own SDK/beacon traffic as anonymous \"node\"\n * visits. Self-identification is the platform's own medicine.\n *\n * Browser note: some browsers filter the User-Agent request header\n * silently (never throwing), so this is a no-op there — which is correct:\n * in a browser the page's real UA is the honest identity.\n */\n\nimport { SDK_VERSION } from './version';\n\nexport const SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;\n\n/**\n * Add the SDK User-Agent without changing the SHAPE of the caller's headers\n * (plain records stay plain records) — interceptors, adapters, and tests\n * read `init.headers` as a record, and `Headers` would also lowercase names.\n */\nfunction withUserAgent(initHeaders: HeadersInit | undefined): HeadersInit {\n if (!initHeaders) return { 'user-agent': SDK_USER_AGENT };\n if (initHeaders instanceof Headers) {\n const copy = new Headers(initHeaders);\n if (!copy.has('user-agent')) copy.set('user-agent', SDK_USER_AGENT);\n return copy;\n }\n if (Array.isArray(initHeaders)) {\n const has = initHeaders.some(([name]) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : [...initHeaders, ['user-agent', SDK_USER_AGENT]];\n }\n const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === 'user-agent');\n return has ? initHeaders : { 'user-agent': SDK_USER_AGENT, ...initHeaders };\n}\n\nexport const sdkFetch: typeof fetch = (input, init) =>\n fetch(input, { ...init, headers: withUserAgent(init?.headers) });\n","/**\n * 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\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 * 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 */\nexport const DEFAULT_EDGE_CONFIG: EdgeConfig = Object.freeze({\n version: 1,\n mode: 'observe',\n depth: 'classify',\n pathRules: [],\n sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),\n failurePosture: 'open',\n}) as EdgeConfig;\n\nconst EDGE_MODES: readonly string[] = ['observe', 'enforce'];\nconst EDGE_DEPTHS: readonly string[] = ['classify', 'authenticate', 'authorize'];\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 }\n const sampling = v.sampling as Record<string, unknown> | undefined;\n if (\n typeof sampling !== 'object' ||\n sampling === null ||\n typeof sampling.anonymousBeaconRate !== 'number' ||\n sampling.anonymousBeaconRate < 0 ||\n sampling.anonymousBeaconRate > 1\n ) {\n return false;\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/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.0.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\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 * 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 */\nexport const DEFAULT_EDGE_CONFIG: EdgeConfig = Object.freeze({\n version: 1,\n mode: 'observe',\n depth: 'classify',\n pathRules: [],\n sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),\n failurePosture: 'open',\n}) as EdgeConfig;\n\nconst EDGE_MODES: readonly string[] = ['observe', 'enforce'];\nconst EDGE_DEPTHS: readonly string[] = ['classify', 'authenticate', 'authorize'];\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 }\n const sampling = v.sampling as Record<string, unknown> | undefined;\n if (\n typeof sampling !== 'object' ||\n sampling === null ||\n typeof sampling.anonymousBeaconRate !== 'number' ||\n sampling.anonymousBeaconRate < 0 ||\n sampling.anonymousBeaconRate > 1\n ) {\n return false;\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,6 +1,6 @@
|
|
|
1
1
|
import { EdgeMode, EdgeVerificationDepth, EdgeConfig } from '../edge-config.mjs';
|
|
2
2
|
import { PlatformFingerprint } from '../platform-signatures.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import { e as CommerceArtifactsPayload, v as VerificationRequest } from '../types-CSHcByFX.mjs';
|
|
4
4
|
import { verify } from '../verify.mjs';
|
|
5
5
|
import { ObservedMetadata } from '../metadata-capture.mjs';
|
|
6
6
|
|
|
@@ -125,7 +125,6 @@ interface TierOutcome {
|
|
|
125
125
|
trustScore?: number;
|
|
126
126
|
agentId?: string;
|
|
127
127
|
developerId?: string;
|
|
128
|
-
accessLevel?: string;
|
|
129
128
|
};
|
|
130
129
|
sessionId?: string;
|
|
131
130
|
protocol?: string;
|
|
@@ -133,6 +132,11 @@ interface TierOutcome {
|
|
|
133
132
|
message?: string;
|
|
134
133
|
registrationUrl?: string;
|
|
135
134
|
documentationUrl?: string;
|
|
135
|
+
mcp?: {
|
|
136
|
+
endpoint?: string;
|
|
137
|
+
discoveryUrl?: string;
|
|
138
|
+
message?: string;
|
|
139
|
+
};
|
|
136
140
|
};
|
|
137
141
|
stepUp?: {
|
|
138
142
|
pollUrl?: string;
|
|
@@ -298,7 +302,6 @@ declare const ASAT_HEADERS: {
|
|
|
298
302
|
readonly trustScore: "X-AstraSync-Trust-Score";
|
|
299
303
|
readonly agentId: "X-AstraSync-Agent-Id";
|
|
300
304
|
readonly developerId: "X-AstraSync-Developer-Id";
|
|
301
|
-
readonly permissions: "X-AstraSync-Permissions";
|
|
302
305
|
readonly observedDecision: "X-AstraSync-Observed-Decision";
|
|
303
306
|
};
|
|
304
307
|
/**
|
|
@@ -364,6 +367,12 @@ interface DenyBody {
|
|
|
364
367
|
message?: string;
|
|
365
368
|
registrationUrl?: string;
|
|
366
369
|
documentationUrl?: string;
|
|
370
|
+
/** 5.1.0: MCP-connector steering — the keyless, human-approved path. */
|
|
371
|
+
mcp?: {
|
|
372
|
+
endpoint: string;
|
|
373
|
+
discoveryUrl?: string;
|
|
374
|
+
message: string;
|
|
375
|
+
};
|
|
367
376
|
};
|
|
368
377
|
};
|
|
369
378
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EdgeMode, EdgeVerificationDepth, EdgeConfig } from '../edge-config.js';
|
|
2
2
|
import { PlatformFingerprint } from '../platform-signatures.js';
|
|
3
|
-
import {
|
|
3
|
+
import { e as CommerceArtifactsPayload, v as VerificationRequest } from '../types-CNBaZKxY.js';
|
|
4
4
|
import { verify } from '../verify.js';
|
|
5
5
|
import { ObservedMetadata } from '../metadata-capture.js';
|
|
6
6
|
|
|
@@ -125,7 +125,6 @@ interface TierOutcome {
|
|
|
125
125
|
trustScore?: number;
|
|
126
126
|
agentId?: string;
|
|
127
127
|
developerId?: string;
|
|
128
|
-
accessLevel?: string;
|
|
129
128
|
};
|
|
130
129
|
sessionId?: string;
|
|
131
130
|
protocol?: string;
|
|
@@ -133,6 +132,11 @@ interface TierOutcome {
|
|
|
133
132
|
message?: string;
|
|
134
133
|
registrationUrl?: string;
|
|
135
134
|
documentationUrl?: string;
|
|
135
|
+
mcp?: {
|
|
136
|
+
endpoint?: string;
|
|
137
|
+
discoveryUrl?: string;
|
|
138
|
+
message?: string;
|
|
139
|
+
};
|
|
136
140
|
};
|
|
137
141
|
stepUp?: {
|
|
138
142
|
pollUrl?: string;
|
|
@@ -298,7 +302,6 @@ declare const ASAT_HEADERS: {
|
|
|
298
302
|
readonly trustScore: "X-AstraSync-Trust-Score";
|
|
299
303
|
readonly agentId: "X-AstraSync-Agent-Id";
|
|
300
304
|
readonly developerId: "X-AstraSync-Developer-Id";
|
|
301
|
-
readonly permissions: "X-AstraSync-Permissions";
|
|
302
305
|
readonly observedDecision: "X-AstraSync-Observed-Decision";
|
|
303
306
|
};
|
|
304
307
|
/**
|
|
@@ -364,6 +367,12 @@ interface DenyBody {
|
|
|
364
367
|
message?: string;
|
|
365
368
|
registrationUrl?: string;
|
|
366
369
|
documentationUrl?: string;
|
|
370
|
+
/** 5.1.0: MCP-connector steering — the keyless, human-approved path. */
|
|
371
|
+
mcp?: {
|
|
372
|
+
endpoint: string;
|
|
373
|
+
discoveryUrl?: string;
|
|
374
|
+
message: string;
|
|
375
|
+
};
|
|
367
376
|
};
|
|
368
377
|
};
|
|
369
378
|
}
|
package/dist/edge-core/index.js
CHANGED
|
@@ -43,7 +43,7 @@ __export(edge_core_exports, {
|
|
|
43
43
|
module.exports = __toCommonJS(edge_core_exports);
|
|
44
44
|
|
|
45
45
|
// src/version.ts
|
|
46
|
-
var SDK_VERSION = "
|
|
46
|
+
var SDK_VERSION = "5.0.0";
|
|
47
47
|
|
|
48
48
|
// src/http.ts
|
|
49
49
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -354,7 +354,7 @@ function detectPlatformFingerprint(input) {
|
|
|
354
354
|
return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
// src/
|
|
357
|
+
// src/trust-levels.ts
|
|
358
358
|
function getTrustLevel(score) {
|
|
359
359
|
if (score >= 80) return "PLATINUM";
|
|
360
360
|
if (score >= 60) return "GOLD";
|
|
@@ -372,11 +372,6 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
372
372
|
// src/verify.ts
|
|
373
373
|
var DEFAULT_CONFIG = {
|
|
374
374
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
375
|
-
// v2.3.9 (defect #30): default for unconfigured callers is `'none'` (no
|
|
376
|
-
// access). Pre-rename this defaulted to `'guidance'`, which combined with
|
|
377
|
-
// a route gated at `'guidance'` to silently let unverified traffic
|
|
378
|
-
// through (`hasMinimumAccess('guidance', 'guidance') === true`).
|
|
379
|
-
defaultAccessLevel: "none",
|
|
380
375
|
// minTrustScore + minTrustScoreForFull deprecated in v2.3.0 — server decides.
|
|
381
376
|
// cacheTtl deliberately unset. When undefined, cacheResult applies the 60s
|
|
382
377
|
// default; the caller's explicit value is honoured for cacheable results.
|
|
@@ -533,6 +528,14 @@ function extractCredentials(headers, query) {
|
|
|
533
528
|
}
|
|
534
529
|
return credentials;
|
|
535
530
|
}
|
|
531
|
+
function mcpGuidance(urls) {
|
|
532
|
+
if (!urls?.mcpEndpoint) return void 0;
|
|
533
|
+
return {
|
|
534
|
+
endpoint: urls.mcpEndpoint,
|
|
535
|
+
discoveryUrl: urls.mcpDiscoveryUrl,
|
|
536
|
+
message: "If you are operating inside an MCP-capable host (Claude, ChatGPT, Cursor, or any MCP client), add the AstraSync MCP connector instead of integrating these URLs by hand: registration through the connector is KEYLESS and HUMAN-APPROVED \u2014 your human approves in an in-chat card, and you never see, hold, or transmit an API key, password, or private key."
|
|
537
|
+
};
|
|
538
|
+
}
|
|
536
539
|
function createGuidanceResponse(_config, reason, options = {}) {
|
|
537
540
|
const source = options.source ?? "no_credentials";
|
|
538
541
|
const isApiError = source === "api_error";
|
|
@@ -559,11 +562,17 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
559
562
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
560
563
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
561
564
|
steps: [
|
|
565
|
+
// MCP steering leads when the hosted bridge is advertised — the
|
|
566
|
+
// connector is the agent-actionable path (keyless, human-approved).
|
|
567
|
+
...urls?.mcpEndpoint ? [
|
|
568
|
+
`Fastest path (inside an MCP host): add the AstraSync MCP connector at ${urls.mcpEndpoint} \u2014 keyless, human-approved registration`
|
|
569
|
+
] : [],
|
|
562
570
|
"Register for an AstraSync account",
|
|
563
571
|
"Create and register your agent",
|
|
564
572
|
"Add your ASTRA-ID to request headers",
|
|
565
573
|
"Retry your request"
|
|
566
|
-
]
|
|
574
|
+
],
|
|
575
|
+
...mcpGuidance(urls) && { mcp: mcpGuidance(urls) }
|
|
567
576
|
};
|
|
568
577
|
return {
|
|
569
578
|
// Round-18 G4: createGuidanceResponse fires for unverified-agent path or
|
|
@@ -571,13 +580,6 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
571
580
|
// policy is not evaluated (we never reached the gate).
|
|
572
581
|
identityVerified: false,
|
|
573
582
|
policyAllowed: false,
|
|
574
|
-
// v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.
|
|
575
|
-
// Adapters additionally short-circuit on `!identityVerified ||
|
|
576
|
-
// !policyAllowed` before the gate check, but the access level still has
|
|
577
|
-
// to be honest at the data layer so downstream consumers (SDK adapters
|
|
578
|
-
// in other languages, custom integrations) inherit the correct
|
|
579
|
-
// semantics.
|
|
580
|
-
accessLevel: "none",
|
|
581
583
|
guidance,
|
|
582
584
|
denialReasons: reason ? [reason] : ["No valid agent credentials provided"],
|
|
583
585
|
// Round-10 (#47, O5): on API-error fallback, surface a typed failure so
|
|
@@ -671,7 +673,6 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
671
673
|
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
672
674
|
access: {
|
|
673
675
|
allowed: false,
|
|
674
|
-
accessLevel: "none",
|
|
675
676
|
reason: "endpoint_deactivated",
|
|
676
677
|
failures: [
|
|
677
678
|
{
|
|
@@ -734,7 +735,7 @@ async function verify(config, request, options) {
|
|
|
734
735
|
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
735
736
|
deprecationWarningShown = true;
|
|
736
737
|
console.warn(
|
|
737
|
-
"[VerificationGateway] minTrustScore / minTrustScoreForFull are deprecated in v2.3.0 and have no effect. Server is now the single source of truth for access
|
|
738
|
+
"[VerificationGateway] minTrustScore / minTrustScoreForFull are deprecated in v2.3.0 and have no effect. Server is now the single source of truth for access decisions (verified ID + runtime challenge + PDLSS + trust score). To gate access to an endpoint, configure the endpoint's trust_score_requirement server-side."
|
|
738
739
|
);
|
|
739
740
|
}
|
|
740
741
|
if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache) {
|
|
@@ -780,14 +781,6 @@ async function verify(config, request, options) {
|
|
|
780
781
|
const result2 = {
|
|
781
782
|
identityVerified: idVerifiedFromBackend,
|
|
782
783
|
policyAllowed: false,
|
|
783
|
-
// v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.
|
|
784
|
-
// Pre-rename this hardcoded `'guidance'`, which conflated with the
|
|
785
|
-
// colocated `guidance: {...}` help-payload object below and let
|
|
786
|
-
// denied requests pass any route gated at `'guidance'` because
|
|
787
|
-
// `hasMinimumAccess('guidance', 'guidance') === true`. Adapters now
|
|
788
|
-
// ALSO short-circuit on `!identityVerified || !policyAllowed` before
|
|
789
|
-
// the gate check — belt-and-braces.
|
|
790
|
-
accessLevel: "none",
|
|
791
784
|
// 3.6.0: the server now emits top-level denialReasons on every deny
|
|
792
785
|
// path — prefer it verbatim; the failure-message reconstruction remains
|
|
793
786
|
// as the fallback for older backends.
|
|
@@ -798,7 +791,10 @@ async function verify(config, request, options) {
|
|
|
798
791
|
guidance: {
|
|
799
792
|
message: apiResponse.access?.reason || "Access denied by PDLSS policy",
|
|
800
793
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
801
|
-
documentationUrl: urls?.documentationUrl ?? ""
|
|
794
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
795
|
+
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
796
|
+
// agents to the connector (this is what the edge deny body surfaces).
|
|
797
|
+
...mcpGuidance(urls) && { mcp: mcpGuidance(urls) }
|
|
802
798
|
},
|
|
803
799
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
804
800
|
// Extract sessionId so decisions can be recorded for denials too
|
|
@@ -836,7 +832,6 @@ async function verify(config, request, options) {
|
|
|
836
832
|
trustScore: apiResponse.organization.trustScore
|
|
837
833
|
} : void 0;
|
|
838
834
|
const verificationContext = apiResponse.verificationContext;
|
|
839
|
-
const accessLevel = apiResponse.access?.accessLevel ?? "standard";
|
|
840
835
|
const result = {
|
|
841
836
|
// 4.5.0 (probe F1): read idVerified from verificationContext with the
|
|
842
837
|
// SAME absent⇒false default as the deny branch. The previous `!== false`
|
|
@@ -848,7 +843,6 @@ async function verify(config, request, options) {
|
|
|
848
843
|
// this load-bearing must never be defaulted on.
|
|
849
844
|
identityVerified: apiResponse.verificationContext?.idVerified === true,
|
|
850
845
|
policyAllowed: true,
|
|
851
|
-
accessLevel,
|
|
852
846
|
agent,
|
|
853
847
|
developer,
|
|
854
848
|
organization,
|
|
@@ -875,7 +869,6 @@ async function verify(config, request, options) {
|
|
|
875
869
|
};
|
|
876
870
|
if (result.recommendation === "deny") {
|
|
877
871
|
result.policyAllowed = false;
|
|
878
|
-
result.accessLevel = "none";
|
|
879
872
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Access denied by AstraSync recommendation"];
|
|
880
873
|
result.guidance = result.runtimeChallenge ? {
|
|
881
874
|
message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
|
|
@@ -1164,8 +1157,17 @@ var PROTOCOL_MESSAGES = {
|
|
|
1164
1157
|
"platform-ua": "This traffic was identified as a platform AI agent without AstraSync credentials. Register the agent to access this property with a verified identity.",
|
|
1165
1158
|
"x-astra": "The presented AstraSync credentials failed verification or were denied by this property\u2019s access policy."
|
|
1166
1159
|
};
|
|
1160
|
+
function fallbackMcpEndpoint(origin) {
|
|
1161
|
+
try {
|
|
1162
|
+
const host = new URL(origin).hostname.replace(/^www\./, "");
|
|
1163
|
+
if (host === "astrasync.ai" || host.endsWith(".astrasync.ai")) return `https://mcp.${host}/mcp`;
|
|
1164
|
+
} catch {
|
|
1165
|
+
}
|
|
1166
|
+
return void 0;
|
|
1167
|
+
}
|
|
1167
1168
|
function buildDenyBody(outcome, apiBaseUrl) {
|
|
1168
1169
|
const origin = apiBaseUrl.replace(/\/api\/?$/, "");
|
|
1170
|
+
const mcpEndpoint = outcome.guidance?.mcp?.endpoint || fallbackMcpEndpoint(origin);
|
|
1169
1171
|
const protocolMessage = (outcome.protocol && PROTOCOL_MESSAGES[outcome.protocol]) ?? "This request could not be verified by AstraSync.";
|
|
1170
1172
|
return {
|
|
1171
1173
|
success: false,
|
|
@@ -1182,7 +1184,14 @@ function buildDenyBody(outcome, apiBaseUrl) {
|
|
|
1182
1184
|
// well-known cache is cold — empty means "use the fallback".
|
|
1183
1185
|
message: outcome.guidance?.message || "Register this agent with AstraSync to receive verifiable credentials.",
|
|
1184
1186
|
registrationUrl: outcome.guidance?.registrationUrl || `${origin}/register`,
|
|
1185
|
-
documentationUrl: outcome.guidance?.documentationUrl || `${origin}/docs/edge-adapter
|
|
1187
|
+
documentationUrl: outcome.guidance?.documentationUrl || `${origin}/docs/edge-adapter`,
|
|
1188
|
+
...mcpEndpoint && {
|
|
1189
|
+
mcp: {
|
|
1190
|
+
endpoint: mcpEndpoint,
|
|
1191
|
+
discoveryUrl: outcome.guidance?.mcp?.discoveryUrl || `${mcpEndpoint.replace(/\/mcp$/, "")}/.well-known/mcp.json`,
|
|
1192
|
+
message: outcome.guidance?.mcp?.message || "Inside an MCP host? Add the AstraSync MCP connector at this endpoint instead of the registration URL \u2014 registration through the connector is keyless and human-approved (your human approves in an in-chat card; you never handle credentials)."
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1186
1195
|
}
|
|
1187
1196
|
}
|
|
1188
1197
|
};
|
|
@@ -1222,7 +1231,6 @@ var ASAT_HEADERS = {
|
|
|
1222
1231
|
trustScore: "X-AstraSync-Trust-Score",
|
|
1223
1232
|
agentId: "X-AstraSync-Agent-Id",
|
|
1224
1233
|
developerId: "X-AstraSync-Developer-Id",
|
|
1225
|
-
permissions: "X-AstraSync-Permissions",
|
|
1226
1234
|
observedDecision: "X-AstraSync-Observed-Decision"
|
|
1227
1235
|
};
|
|
1228
1236
|
var STRIP_HEADER_PREFIXES = ["x-astrasync-", "x-astra-verified-hop"];
|
|
@@ -1271,12 +1279,11 @@ function applyOutcome(options, outcome, policy) {
|
|
|
1271
1279
|
}
|
|
1272
1280
|
function asatHeaders(outcome) {
|
|
1273
1281
|
if (!outcome.asat) return {};
|
|
1274
|
-
const { trustScore, agentId, developerId
|
|
1282
|
+
const { trustScore, agentId, developerId } = outcome.asat;
|
|
1275
1283
|
return {
|
|
1276
1284
|
...trustScore !== void 0 && { [ASAT_HEADERS.trustScore]: String(trustScore) },
|
|
1277
1285
|
...agentId && { [ASAT_HEADERS.agentId]: agentId },
|
|
1278
|
-
...developerId && { [ASAT_HEADERS.developerId]: developerId }
|
|
1279
|
-
...accessLevel && { [ASAT_HEADERS.permissions]: accessLevel }
|
|
1286
|
+
...developerId && { [ASAT_HEADERS.developerId]: developerId }
|
|
1280
1287
|
};
|
|
1281
1288
|
}
|
|
1282
1289
|
function verifiedHopHeader(outcome) {
|
|
@@ -1484,8 +1491,7 @@ function outcomeFromVerifyResult(classification, result) {
|
|
|
1484
1491
|
outcome.asat = {
|
|
1485
1492
|
trustScore: result.agent?.trustScore,
|
|
1486
1493
|
agentId: result.agent?.astraId,
|
|
1487
|
-
developerId: result.developer?.astradId
|
|
1488
|
-
accessLevel: result.accessLevel
|
|
1494
|
+
developerId: result.developer?.astradId
|
|
1489
1495
|
};
|
|
1490
1496
|
return outcome;
|
|
1491
1497
|
}
|