@agent-score/commerce 2.4.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +19 -17
  2. package/dist/challenge/index.d.mts +31 -3
  3. package/dist/challenge/index.d.ts +31 -3
  4. package/dist/challenge/index.js +25 -9
  5. package/dist/challenge/index.js.map +1 -1
  6. package/dist/challenge/index.mjs +25 -9
  7. package/dist/challenge/index.mjs.map +1 -1
  8. package/dist/{checkout-B-MIzYzW.d.ts → checkout-C4RD7M0Z.d.ts} +117 -13
  9. package/dist/{checkout-Bn7ZKIBD.d.mts → checkout-CzB9f_jf.d.mts} +117 -13
  10. package/dist/core.d.mts +40 -7
  11. package/dist/core.d.ts +40 -7
  12. package/dist/core.js +52 -27
  13. package/dist/core.js.map +1 -1
  14. package/dist/core.mjs +52 -27
  15. package/dist/core.mjs.map +1 -1
  16. package/dist/discovery/index.d.mts +33 -3
  17. package/dist/discovery/index.d.ts +33 -3
  18. package/dist/discovery/index.js +50 -16
  19. package/dist/discovery/index.js.map +1 -1
  20. package/dist/discovery/index.mjs +49 -16
  21. package/dist/discovery/index.mjs.map +1 -1
  22. package/dist/gate-D2RP6bZg.d.ts +307 -0
  23. package/dist/gate-GCXy4FHI.d.mts +307 -0
  24. package/dist/identity/express.d.mts +28 -1
  25. package/dist/identity/express.d.ts +28 -1
  26. package/dist/identity/express.js +714 -31
  27. package/dist/identity/express.js.map +1 -1
  28. package/dist/identity/express.mjs +710 -30
  29. package/dist/identity/express.mjs.map +1 -1
  30. package/dist/identity/fastify.d.mts +14 -2
  31. package/dist/identity/fastify.d.ts +14 -2
  32. package/dist/identity/fastify.js +727 -31
  33. package/dist/identity/fastify.js.map +1 -1
  34. package/dist/identity/fastify.mjs +723 -30
  35. package/dist/identity/fastify.mjs.map +1 -1
  36. package/dist/identity/hono.d.mts +28 -1
  37. package/dist/identity/hono.d.ts +28 -1
  38. package/dist/identity/hono.js +696 -33
  39. package/dist/identity/hono.js.map +1 -1
  40. package/dist/identity/hono.mjs +692 -32
  41. package/dist/identity/hono.mjs.map +1 -1
  42. package/dist/identity/nextjs.d.mts +5 -0
  43. package/dist/identity/nextjs.d.ts +5 -0
  44. package/dist/identity/nextjs.js +695 -30
  45. package/dist/identity/nextjs.js.map +1 -1
  46. package/dist/identity/nextjs.mjs +691 -29
  47. package/dist/identity/nextjs.mjs.map +1 -1
  48. package/dist/identity/policy.d.mts +21 -2
  49. package/dist/identity/policy.d.ts +21 -2
  50. package/dist/identity/policy.js +22 -1
  51. package/dist/identity/policy.js.map +1 -1
  52. package/dist/identity/policy.mjs +21 -1
  53. package/dist/identity/policy.mjs.map +1 -1
  54. package/dist/identity/web.d.mts +26 -1
  55. package/dist/identity/web.d.ts +26 -1
  56. package/dist/identity/web.js +695 -30
  57. package/dist/identity/web.js.map +1 -1
  58. package/dist/identity/web.mjs +691 -29
  59. package/dist/identity/web.mjs.map +1 -1
  60. package/dist/index.d.mts +165 -4
  61. package/dist/index.d.ts +165 -4
  62. package/dist/index.js +3103 -534
  63. package/dist/index.js.map +1 -1
  64. package/dist/index.mjs +3086 -545
  65. package/dist/index.mjs.map +1 -1
  66. package/dist/payment/index.d.mts +1 -1
  67. package/dist/payment/index.d.ts +1 -1
  68. package/dist/payment/index.js +10 -2
  69. package/dist/payment/index.js.map +1 -1
  70. package/dist/payment/index.mjs +10 -2
  71. package/dist/payment/index.mjs.map +1 -1
  72. package/dist/stripe-multichain/index.js +1614 -266
  73. package/dist/stripe-multichain/index.js.map +1 -1
  74. package/dist/stripe-multichain/index.mjs +1614 -266
  75. package/dist/stripe-multichain/index.mjs.map +1 -1
  76. package/dist/types-D1slMt0H.d.mts +137 -0
  77. package/dist/types-D1slMt0H.d.ts +137 -0
  78. package/dist/{wwwauthenticate-CVaGUMjU.d.mts → wwwauthenticate-Dgm-_af9.d.mts} +11 -1
  79. package/dist/{wwwauthenticate-CVaGUMjU.d.ts → wwwauthenticate-Dgm-_af9.d.ts} +11 -1
  80. package/package.json +26 -21
@@ -1,4 +1,5 @@
1
1
  import { DenialReason, AgentScoreCoreOptions } from '../core.mjs';
2
+ import '@agent-score/sdk';
2
3
  import '../signer-3FAit11j.mjs';
3
4
 
4
5
  /**
@@ -7,7 +8,7 @@ import '../signer-3FAit11j.mjs';
7
8
  * A *policy* is a small bag of fields describing what identity the merchant wants
8
9
  * verified for a given resource:
9
10
  *
10
- * - `enforcement`: `"hard"` (today's wine path — 403 on miss) or `"soft"` (gate
11
+ * - `enforcement`: `"hard"` (the regulated-goods path — 403 on miss) or `"soft"` (gate
11
12
  * denial is swallowed; the order completes with a degraded `identity_status`).
12
13
  * `null` / absent = no gate at all.
13
14
  * - `requireKyc` / `requireSanctionsClear` / `minAge`: passed through to the
@@ -41,6 +42,8 @@ interface PolicyBlock {
41
42
  requireKyc?: boolean;
42
43
  requireSanctionsClear?: boolean;
43
44
  minAge?: number;
45
+ /** Buyer-verified country blocklist (`["RU", "KP", ...]`) — these jurisdictions are denied. */
46
+ blockedJurisdictions?: readonly string[];
44
47
  allowedJurisdictions?: readonly string[];
45
48
  allowedShippingCountries?: readonly string[];
46
49
  allowedShippingStates?: readonly string[];
@@ -76,6 +79,15 @@ declare function buildGateFromPolicy(policy: PolicyBlock | null | undefined, bas
76
79
  apiKey: string;
77
80
  baseUrl?: string;
78
81
  }): AgentScoreCoreOptions | null;
82
+ /**
83
+ * True when a gate denial body indicates an OFAC SDN sanctions hit (or unavailable screen).
84
+ *
85
+ * Inspects the flat denial body emitted by `denialReasonToBody`: a `wallet_not_trusted`
86
+ * (or signer-sanctions) deny carries the sanctions reason in `reasons` / `decision_reasons`,
87
+ * or surfaces it as a top-level `error.code`. Used by {@link runGateWithEnforcement} so soft
88
+ * mode can downgrade non-sanctions denials while leaving a sanctions deny terminal.
89
+ */
90
+ declare function isSanctionsDenial(body: Record<string, unknown> | null | undefined): boolean;
79
91
  /**
80
92
  * Run a per-framework gate middleware respecting the enforcement mode.
81
93
  *
@@ -93,6 +105,13 @@ declare function buildGateFromPolicy(policy: PolicyBlock | null | undefined, bas
93
105
  * - `enforcement="hard"` + denied: status="denied"; caller propagates denialStatus + denialBody.
94
106
  * - `enforcement="soft"` + denied: swallow; status="unverified".
95
107
  * - accepted: status="verified".
108
+ *
109
+ * **Sanctions are never swallowed.** Soft mode is a commercial knob — it lets a merchant
110
+ * accept an order from an agent that didn't satisfy KYC / age / jurisdiction (stamping a
111
+ * degraded `identity_status` for ops). But an OFAC SDN sanctions deny is strict-liability:
112
+ * settling for a sanctioned wallet is a violation regardless of the merchant's posture. So a
113
+ * denial whose body indicates sanctions ({@link isSanctionsDenial}) returns `status="denied"`
114
+ * even under `enforcement="soft"`; soft only downgrades the non-sanctions reasons.
96
115
  */
97
116
  declare function runGateWithEnforcement(enforcement: EnforcementMode | undefined, runGate: (() => Promise<{
98
117
  ok: true;
@@ -149,4 +168,4 @@ declare function validateShippingAgainstPolicy(opts: {
149
168
  stateMessage?: string;
150
169
  }): void;
151
170
 
152
- export { type EnforcementMode, type GateResult, type IdentityStatus, type PolicyBlock, buildGateFromPolicy, runGateWithEnforcement, shippingCountryAllowed, shippingStateAllowed, validateShippingAgainstPolicy };
171
+ export { type EnforcementMode, type GateResult, type IdentityStatus, type PolicyBlock, buildGateFromPolicy, isSanctionsDenial, runGateWithEnforcement, shippingCountryAllowed, shippingStateAllowed, validateShippingAgainstPolicy };
@@ -1,4 +1,5 @@
1
1
  import { DenialReason, AgentScoreCoreOptions } from '../core.js';
2
+ import '@agent-score/sdk';
2
3
  import '../signer-3FAit11j.js';
3
4
 
4
5
  /**
@@ -7,7 +8,7 @@ import '../signer-3FAit11j.js';
7
8
  * A *policy* is a small bag of fields describing what identity the merchant wants
8
9
  * verified for a given resource:
9
10
  *
10
- * - `enforcement`: `"hard"` (today's wine path — 403 on miss) or `"soft"` (gate
11
+ * - `enforcement`: `"hard"` (the regulated-goods path — 403 on miss) or `"soft"` (gate
11
12
  * denial is swallowed; the order completes with a degraded `identity_status`).
12
13
  * `null` / absent = no gate at all.
13
14
  * - `requireKyc` / `requireSanctionsClear` / `minAge`: passed through to the
@@ -41,6 +42,8 @@ interface PolicyBlock {
41
42
  requireKyc?: boolean;
42
43
  requireSanctionsClear?: boolean;
43
44
  minAge?: number;
45
+ /** Buyer-verified country blocklist (`["RU", "KP", ...]`) — these jurisdictions are denied. */
46
+ blockedJurisdictions?: readonly string[];
44
47
  allowedJurisdictions?: readonly string[];
45
48
  allowedShippingCountries?: readonly string[];
46
49
  allowedShippingStates?: readonly string[];
@@ -76,6 +79,15 @@ declare function buildGateFromPolicy(policy: PolicyBlock | null | undefined, bas
76
79
  apiKey: string;
77
80
  baseUrl?: string;
78
81
  }): AgentScoreCoreOptions | null;
82
+ /**
83
+ * True when a gate denial body indicates an OFAC SDN sanctions hit (or unavailable screen).
84
+ *
85
+ * Inspects the flat denial body emitted by `denialReasonToBody`: a `wallet_not_trusted`
86
+ * (or signer-sanctions) deny carries the sanctions reason in `reasons` / `decision_reasons`,
87
+ * or surfaces it as a top-level `error.code`. Used by {@link runGateWithEnforcement} so soft
88
+ * mode can downgrade non-sanctions denials while leaving a sanctions deny terminal.
89
+ */
90
+ declare function isSanctionsDenial(body: Record<string, unknown> | null | undefined): boolean;
79
91
  /**
80
92
  * Run a per-framework gate middleware respecting the enforcement mode.
81
93
  *
@@ -93,6 +105,13 @@ declare function buildGateFromPolicy(policy: PolicyBlock | null | undefined, bas
93
105
  * - `enforcement="hard"` + denied: status="denied"; caller propagates denialStatus + denialBody.
94
106
  * - `enforcement="soft"` + denied: swallow; status="unverified".
95
107
  * - accepted: status="verified".
108
+ *
109
+ * **Sanctions are never swallowed.** Soft mode is a commercial knob — it lets a merchant
110
+ * accept an order from an agent that didn't satisfy KYC / age / jurisdiction (stamping a
111
+ * degraded `identity_status` for ops). But an OFAC SDN sanctions deny is strict-liability:
112
+ * settling for a sanctioned wallet is a violation regardless of the merchant's posture. So a
113
+ * denial whose body indicates sanctions ({@link isSanctionsDenial}) returns `status="denied"`
114
+ * even under `enforcement="soft"`; soft only downgrades the non-sanctions reasons.
96
115
  */
97
116
  declare function runGateWithEnforcement(enforcement: EnforcementMode | undefined, runGate: (() => Promise<{
98
117
  ok: true;
@@ -149,4 +168,4 @@ declare function validateShippingAgainstPolicy(opts: {
149
168
  stateMessage?: string;
150
169
  }): void;
151
170
 
152
- export { type EnforcementMode, type GateResult, type IdentityStatus, type PolicyBlock, buildGateFromPolicy, runGateWithEnforcement, shippingCountryAllowed, shippingStateAllowed, validateShippingAgainstPolicy };
171
+ export { type EnforcementMode, type GateResult, type IdentityStatus, type PolicyBlock, buildGateFromPolicy, isSanctionsDenial, runGateWithEnforcement, shippingCountryAllowed, shippingStateAllowed, validateShippingAgainstPolicy };
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var policy_exports = {};
22
22
  __export(policy_exports, {
23
23
  buildGateFromPolicy: () => buildGateFromPolicy,
24
+ isSanctionsDenial: () => isSanctionsDenial,
24
25
  runGateWithEnforcement: () => runGateWithEnforcement,
25
26
  shippingCountryAllowed: () => shippingCountryAllowed,
26
27
  shippingStateAllowed: () => shippingStateAllowed,
@@ -55,16 +56,35 @@ function buildGateFromPolicy(policy, base) {
55
56
  requireSanctionsClear: policy.requireSanctionsClear
56
57
  },
57
58
  ...policy.minAge !== void 0 && { minAge: policy.minAge },
59
+ ...policy.blockedJurisdictions !== void 0 && {
60
+ blockedJurisdictions: [...policy.blockedJurisdictions]
61
+ },
58
62
  ...policy.allowedJurisdictions !== void 0 && {
59
63
  allowedJurisdictions: [...policy.allowedJurisdictions]
60
64
  }
61
65
  };
62
66
  }
67
+ var SANCTIONS_DENIAL_REASONS = /* @__PURE__ */ new Set([
68
+ "sanctions_flagged",
69
+ "sanctions_check_unavailable"
70
+ ]);
71
+ function isSanctionsDenial(body) {
72
+ if (!body || typeof body !== "object") return false;
73
+ for (const key of ["reasons", "decision_reasons"]) {
74
+ const raw = body[key];
75
+ if (Array.isArray(raw) && raw.some((r) => typeof r === "string" && SANCTIONS_DENIAL_REASONS.has(r))) {
76
+ return true;
77
+ }
78
+ }
79
+ const error = body.error;
80
+ const code = error && typeof error === "object" ? error.code : void 0;
81
+ return typeof code === "string" && SANCTIONS_DENIAL_REASONS.has(code);
82
+ }
63
83
  async function runGateWithEnforcement(enforcement, runGate) {
64
84
  if (!runGate || !enforcement) return { status: "anonymous" };
65
85
  const outcome = await runGate();
66
86
  if (outcome.ok) return { status: "verified" };
67
- if (enforcement === "hard") {
87
+ if (enforcement === "hard" || isSanctionsDenial(outcome.body)) {
68
88
  return {
69
89
  status: "denied",
70
90
  denialStatus: outcome.status,
@@ -112,6 +132,7 @@ function validateShippingAgainstPolicy(opts) {
112
132
  // Annotate the CommonJS export names for ESM import in node:
113
133
  0 && (module.exports = {
114
134
  buildGateFromPolicy,
135
+ isSanctionsDenial,
115
136
  runGateWithEnforcement,
116
137
  shippingCountryAllowed,
117
138
  shippingStateAllowed,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/identity/policy.ts","../../src/errors.ts"],"sourcesContent":["/**\n * Per-product / per-tier compliance policy helpers.\n *\n * A *policy* is a small bag of fields describing what identity the merchant wants\n * verified for a given resource:\n *\n * - `enforcement`: `\"hard\"` (today's wine path — 403 on miss) or `\"soft\"` (gate\n * denial is swallowed; the order completes with a degraded `identity_status`).\n * `null` / absent = no gate at all.\n * - `requireKyc` / `requireSanctionsClear` / `minAge`: passed through to the\n * per-framework `agentscoreGate(...)` factory.\n * - `allowedJurisdictions`: buyer-verified country list (`[\"US\", \"CA\", ...]`).\n * - `allowedShippingCountries` / `allowedShippingStates`: optional shipping\n * allowlists. State list is only enforced for US shipments.\n *\n * This module ships three primitives:\n *\n * 1. {@link PolicyBlock} — the typed shape.\n * 2. {@link buildGateFromPolicy} — translate a block into the options object the\n * per-framework `agentscoreGate(...)` accepts. Returns `null` when the policy\n * has no enforcement (treat as \"no gate; anonymous OK\").\n * 3. {@link runGateWithEnforcement} — wrap a per-framework middleware in the\n * hard/soft enforcement runner. The middleware is given an `onDenied` shim\n * that captures the denial body and status; the runner returns a structured\n * {@link GateResult} so the vendor decides how to surface it.\n *\n * All three are additive — vendors using `agentscoreGate(...)` directly are\n * unaffected.\n */\n\nimport { CheckoutValidationError } from '../errors';\nimport type { AgentScoreCoreOptions, DenialReason } from '../core.js';\n\n/** Hard = 403 propagates; soft = swallowed + identity_status=\"unverified\". */\nexport type EnforcementMode = 'hard' | 'soft';\n\n/** Per-order trust level captured at settle time. */\nexport type IdentityStatus = 'verified' | 'unverified' | 'anonymous' | 'denied';\n\n/** Compliance fields a merchant attaches per product / per tier. All optional. */\nexport interface PolicyBlock {\n enforcement?: EnforcementMode;\n requireKyc?: boolean;\n requireSanctionsClear?: boolean;\n minAge?: number;\n allowedJurisdictions?: readonly string[];\n allowedShippingCountries?: readonly string[];\n allowedShippingStates?: readonly string[];\n}\n\n/**\n * Outcome of running a gate under an enforcement mode.\n *\n * - `verified`: gate accepted; identity is fully verified for the policy.\n * - `unverified`: soft mode swallowed a gate denial; the agent had *some*\n * identity but didn't meet the policy. Stamp this on the order so\n * ops/analytics can tell apart soft passes from hard passes.\n * - `anonymous`: no gate ran (policy was null / no enforcement).\n * - `denied`: hard mode rejected; the caller must propagate the 403. The\n * `denialBody` and `denialStatus` carry the original gate response so the\n * caller can return it as-is.\n */\nexport interface GateResult {\n status: IdentityStatus;\n denialStatus?: number;\n denialBody?: Record<string, unknown>;\n denialReason?: DenialReason;\n}\n\n/**\n * Translate a {@link PolicyBlock} into the options the per-framework\n * `agentscoreGate(...)` expects. Returns `null` when the block has no\n * `enforcement` set — the caller should treat that as \"no gate; anonymous OK\".\n *\n * Use a fresh gate per request rather than constructing once at module scope\n * when the policy varies per resource (e.g. per product). Each adapter's gate\n * is cheap to instantiate.\n */\nexport function buildGateFromPolicy(\n policy: PolicyBlock | null | undefined,\n base: { apiKey: string; baseUrl?: string },\n): AgentScoreCoreOptions | null {\n if (!policy || !policy.enforcement) return null;\n return {\n apiKey: base.apiKey,\n ...(base.baseUrl !== undefined && { baseUrl: base.baseUrl }),\n ...(policy.requireKyc !== undefined && { requireKyc: policy.requireKyc }),\n ...(policy.requireSanctionsClear !== undefined && {\n requireSanctionsClear: policy.requireSanctionsClear,\n }),\n ...(policy.minAge !== undefined && { minAge: policy.minAge }),\n ...(policy.allowedJurisdictions !== undefined && {\n allowedJurisdictions: [...policy.allowedJurisdictions],\n }),\n };\n}\n\n/**\n * Run a per-framework gate middleware respecting the enforcement mode.\n *\n * The vendor passes:\n * - `gate`: their framework's middleware (Hono `MiddlewareHandler`, Express\n * `(req, res, next) => void`, etc.) — anything that resolves on accept and\n * throws or returns a `Response` on deny.\n * - `runGate`: a thin adapter that calls the middleware with the framework\n * context and returns either `{ ok: true }` (gate accepted) or\n * `{ ok: false, status, body, reason? }` (gate denied with details).\n *\n * `runGateWithEnforcement` wraps that in the hard/soft split:\n *\n * - `gate=null` or `enforcement=null`: no gate fires; status=\"anonymous\".\n * - `enforcement=\"hard\"` + denied: status=\"denied\"; caller propagates denialStatus + denialBody.\n * - `enforcement=\"soft\"` + denied: swallow; status=\"unverified\".\n * - accepted: status=\"verified\".\n */\nexport async function runGateWithEnforcement(\n enforcement: EnforcementMode | undefined,\n runGate: (() => Promise<{ ok: true } | { ok: false; status: number; body: Record<string, unknown>; reason?: DenialReason }>) | null,\n): Promise<GateResult> {\n if (!runGate || !enforcement) return { status: 'anonymous' };\n\n const outcome = await runGate();\n if (outcome.ok) return { status: 'verified' };\n\n if (enforcement === 'hard') {\n return {\n status: 'denied',\n denialStatus: outcome.status,\n denialBody: outcome.body,\n ...(outcome.reason !== undefined && { denialReason: outcome.reason }),\n };\n }\n return {\n status: 'unverified',\n denialStatus: outcome.status,\n denialBody: outcome.body,\n ...(outcome.reason !== undefined && { denialReason: outcome.reason }),\n };\n}\n\n/** NULL policy / NULL allowlist → ship anywhere. Otherwise country must be in the list. */\nexport function shippingCountryAllowed(country: string, policy: PolicyBlock | null | undefined): boolean {\n if (!policy?.allowedShippingCountries || policy.allowedShippingCountries.length === 0) return true;\n const allowed = new Set(policy.allowedShippingCountries.map((c) => c.toUpperCase()));\n return allowed.has(country.toUpperCase());\n}\n\n/**\n * US-state allowlist (e.g. wine).\n *\n * Only enforced for US shipments — non-US shipments are governed by\n * {@link shippingCountryAllowed} independently.\n */\nexport function shippingStateAllowed(\n state: string,\n country: string,\n policy: PolicyBlock | null | undefined,\n): boolean {\n if (!policy?.allowedShippingStates || policy.allowedShippingStates.length === 0) return true;\n if (country.toUpperCase() !== 'US') return true;\n const allowed = new Set(policy.allowedShippingStates.map((s) => s.toUpperCase()));\n return allowed.has(state.toUpperCase());\n}\n\n/**\n * Throw {@link CheckoutValidationError} when shipping isn't allowed by the policy.\n *\n * One-call replacement for the\n *\n * if (!shippingCountryAllowed(...)) throw new CheckoutValidationError(...);\n * if (!shippingStateAllowed(...)) throw new CheckoutValidationError(...);\n *\n * boilerplate every goods merchant writes in their `preValidate` hook.\n *\n * `policy` is a {@link PolicyBlock} (or `null`/`undefined`); NULL policy means\n * \"ship anywhere\" and the function is a no-op. The reason a location is\n * excluded is **merchant-defined**: it might be regulatory (regulated goods\n * + state allowlist), operational (no fulfillment partner), or commercial\n * (fragility, fraud-rate-by-region, etc.) — the helper doesn't assume.\n *\n * `productName` is the user-facing item name surfaced in the error message\n * (\"Cannot ship 'Wine 2020' to NY ...\"). Omit for a generic message.\n *\n * `errorCode` and `errorAction` let merchants override the canonical denial\n * codes if their consumer agents expect different shapes.\n *\n * `countryMessage` / `stateMessage` override the default messages verbatim\n * (use these when the default phrasing isn't right for your consumer agents\n * — e.g. you want to surface the regulatory reason explicitly, or you want\n * the message in a different language).\n */\nexport function validateShippingAgainstPolicy(opts: {\n country: string;\n state: string;\n policy: PolicyBlock | null | undefined;\n productName?: string;\n errorCode?: string;\n errorAction?: string;\n countryMessage?: string;\n stateMessage?: string;\n}): void {\n const code = opts.errorCode ?? 'unsupported_jurisdiction';\n const action = opts.errorAction ?? 'change_shipping_state';\n const item = opts.productName ? `'${opts.productName}'` : 'this item';\n if (!shippingCountryAllowed(opts.country, opts.policy)) {\n throw new CheckoutValidationError({\n code,\n message:\n opts.countryMessage ??\n `We can't ship ${item} to ${opts.country.toUpperCase() || '<unset>'}.`,\n action,\n });\n }\n if (!shippingStateAllowed(opts.state, opts.country, opts.policy)) {\n throw new CheckoutValidationError({\n code,\n message:\n opts.stateMessage ??\n `We can't ship ${item} to ${opts.state.toUpperCase() || '<unset>'}.`,\n action,\n });\n }\n}\n","/**\n * Cross-module typed errors.\n *\n * Lives in its own module so `payment/` and `stripe-multichain/` helpers can\n * throw `CheckoutValidationError` without importing from `checkout.ts` — this\n * also sidesteps tsup's per-entry class duplication.\n *\n * Re-exported from `checkout.ts` and the top-level entry to preserve the\n * public import path.\n */\n\n/**\n * Raised to short-circuit a Checkout flow with a 4xx/5xx envelope. The\n * framework catches this at request-flow boundaries (`preValidate`, recipient\n * minting, settlement dispatch) and emits `{ error, next_steps }` via\n * `buildValidationError` so merchants don't construct response bodies\n * themselves.\n */\nexport class CheckoutValidationError extends Error {\n readonly code: string;\n readonly action: string;\n readonly status: number;\n readonly extra: Record<string, unknown> | undefined;\n constructor(opts: {\n code: string;\n message: string;\n action?: string;\n status?: number;\n extra?: Record<string, unknown>;\n }) {\n super(opts.message);\n this.name = 'CheckoutValidationError';\n this.code = opts.code;\n this.action = opts.action ?? 'fix_request';\n this.status = opts.status ?? 400;\n this.extra = opts.extra;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/identity/policy.ts","../../src/errors.ts"],"sourcesContent":["/**\n * Per-product / per-tier compliance policy helpers.\n *\n * A *policy* is a small bag of fields describing what identity the merchant wants\n * verified for a given resource:\n *\n * - `enforcement`: `\"hard\"` (the regulated-goods path — 403 on miss) or `\"soft\"` (gate\n * denial is swallowed; the order completes with a degraded `identity_status`).\n * `null` / absent = no gate at all.\n * - `requireKyc` / `requireSanctionsClear` / `minAge`: passed through to the\n * per-framework `agentscoreGate(...)` factory.\n * - `allowedJurisdictions`: buyer-verified country list (`[\"US\", \"CA\", ...]`).\n * - `allowedShippingCountries` / `allowedShippingStates`: optional shipping\n * allowlists. State list is only enforced for US shipments.\n *\n * This module ships three primitives:\n *\n * 1. {@link PolicyBlock} — the typed shape.\n * 2. {@link buildGateFromPolicy} — translate a block into the options object the\n * per-framework `agentscoreGate(...)` accepts. Returns `null` when the policy\n * has no enforcement (treat as \"no gate; anonymous OK\").\n * 3. {@link runGateWithEnforcement} — wrap a per-framework middleware in the\n * hard/soft enforcement runner. The middleware is given an `onDenied` shim\n * that captures the denial body and status; the runner returns a structured\n * {@link GateResult} so the vendor decides how to surface it.\n *\n * All three are additive — vendors using `agentscoreGate(...)` directly are\n * unaffected.\n */\n\nimport { CheckoutValidationError } from '../errors';\nimport type { AgentScoreCoreOptions, DenialReason } from '../core.js';\n\n/** Hard = 403 propagates; soft = swallowed + identity_status=\"unverified\". */\nexport type EnforcementMode = 'hard' | 'soft';\n\n/** Per-order trust level captured at settle time. */\nexport type IdentityStatus = 'verified' | 'unverified' | 'anonymous' | 'denied';\n\n/** Compliance fields a merchant attaches per product / per tier. All optional. */\nexport interface PolicyBlock {\n enforcement?: EnforcementMode;\n requireKyc?: boolean;\n requireSanctionsClear?: boolean;\n minAge?: number;\n /** Buyer-verified country blocklist (`[\"RU\", \"KP\", ...]`) — these jurisdictions are denied. */\n blockedJurisdictions?: readonly string[];\n allowedJurisdictions?: readonly string[];\n allowedShippingCountries?: readonly string[];\n allowedShippingStates?: readonly string[];\n}\n\n/**\n * Outcome of running a gate under an enforcement mode.\n *\n * - `verified`: gate accepted; identity is fully verified for the policy.\n * - `unverified`: soft mode swallowed a gate denial; the agent had *some*\n * identity but didn't meet the policy. Stamp this on the order so\n * ops/analytics can tell apart soft passes from hard passes.\n * - `anonymous`: no gate ran (policy was null / no enforcement).\n * - `denied`: hard mode rejected; the caller must propagate the 403. The\n * `denialBody` and `denialStatus` carry the original gate response so the\n * caller can return it as-is.\n */\nexport interface GateResult {\n status: IdentityStatus;\n denialStatus?: number;\n denialBody?: Record<string, unknown>;\n denialReason?: DenialReason;\n}\n\n/**\n * Translate a {@link PolicyBlock} into the options the per-framework\n * `agentscoreGate(...)` expects. Returns `null` when the block has no\n * `enforcement` set — the caller should treat that as \"no gate; anonymous OK\".\n *\n * Use a fresh gate per request rather than constructing once at module scope\n * when the policy varies per resource (e.g. per product). Each adapter's gate\n * is cheap to instantiate.\n */\nexport function buildGateFromPolicy(\n policy: PolicyBlock | null | undefined,\n base: { apiKey: string; baseUrl?: string },\n): AgentScoreCoreOptions | null {\n if (!policy || !policy.enforcement) return null;\n return {\n apiKey: base.apiKey,\n ...(base.baseUrl !== undefined && { baseUrl: base.baseUrl }),\n ...(policy.requireKyc !== undefined && { requireKyc: policy.requireKyc }),\n ...(policy.requireSanctionsClear !== undefined && {\n requireSanctionsClear: policy.requireSanctionsClear,\n }),\n ...(policy.minAge !== undefined && { minAge: policy.minAge }),\n ...(policy.blockedJurisdictions !== undefined && {\n blockedJurisdictions: [...policy.blockedJurisdictions],\n }),\n ...(policy.allowedJurisdictions !== undefined && {\n allowedJurisdictions: [...policy.allowedJurisdictions],\n }),\n };\n}\n\n/**\n * OFAC SDN denial reasons. These are strict-liability: soft enforcement may\n * downgrade KYC / age / jurisdiction misses (the merchant accepts the order with a\n * degraded `identity_status`), but it must NEVER swallow a sanctions deny — falsely\n * settling for a sanctioned wallet is an OFAC violation regardless of the merchant's\n * soft posture. The API emits `sanctions_flagged` in `decision_reasons` for BOTH the\n * operator/wallet SDN hit and the payment-signer OFAC SDN hit;\n * `sanctions_check_unavailable` is the fail-closed unavailable-lookup variant (a\n * missing screen on a strict rail is also a hard deny). Match the canonical strings\n * from `the AgentScore API`.\n */\nconst SANCTIONS_DENIAL_REASONS: ReadonlySet<string> = new Set([\n 'sanctions_flagged',\n 'sanctions_check_unavailable',\n]);\n\n/**\n * True when a gate denial body indicates an OFAC SDN sanctions hit (or unavailable screen).\n *\n * Inspects the flat denial body emitted by `denialReasonToBody`: a `wallet_not_trusted`\n * (or signer-sanctions) deny carries the sanctions reason in `reasons` / `decision_reasons`,\n * or surfaces it as a top-level `error.code`. Used by {@link runGateWithEnforcement} so soft\n * mode can downgrade non-sanctions denials while leaving a sanctions deny terminal.\n */\nexport function isSanctionsDenial(body: Record<string, unknown> | null | undefined): boolean {\n if (!body || typeof body !== 'object') return false;\n for (const key of ['reasons', 'decision_reasons'] as const) {\n const raw = body[key];\n if (Array.isArray(raw) && raw.some((r) => typeof r === 'string' && SANCTIONS_DENIAL_REASONS.has(r))) {\n return true;\n }\n }\n // The signer-sanctions SDN deny may also surface as a top-level error code.\n const error = body.error;\n const code = error && typeof error === 'object' ? (error as Record<string, unknown>).code : undefined;\n return typeof code === 'string' && SANCTIONS_DENIAL_REASONS.has(code);\n}\n\n/**\n * Run a per-framework gate middleware respecting the enforcement mode.\n *\n * The vendor passes:\n * - `gate`: their framework's middleware (Hono `MiddlewareHandler`, Express\n * `(req, res, next) => void`, etc.) — anything that resolves on accept and\n * throws or returns a `Response` on deny.\n * - `runGate`: a thin adapter that calls the middleware with the framework\n * context and returns either `{ ok: true }` (gate accepted) or\n * `{ ok: false, status, body, reason? }` (gate denied with details).\n *\n * `runGateWithEnforcement` wraps that in the hard/soft split:\n *\n * - `gate=null` or `enforcement=null`: no gate fires; status=\"anonymous\".\n * - `enforcement=\"hard\"` + denied: status=\"denied\"; caller propagates denialStatus + denialBody.\n * - `enforcement=\"soft\"` + denied: swallow; status=\"unverified\".\n * - accepted: status=\"verified\".\n *\n * **Sanctions are never swallowed.** Soft mode is a commercial knob — it lets a merchant\n * accept an order from an agent that didn't satisfy KYC / age / jurisdiction (stamping a\n * degraded `identity_status` for ops). But an OFAC SDN sanctions deny is strict-liability:\n * settling for a sanctioned wallet is a violation regardless of the merchant's posture. So a\n * denial whose body indicates sanctions ({@link isSanctionsDenial}) returns `status=\"denied\"`\n * even under `enforcement=\"soft\"`; soft only downgrades the non-sanctions reasons.\n */\nexport async function runGateWithEnforcement(\n enforcement: EnforcementMode | undefined,\n runGate: (() => Promise<{ ok: true } | { ok: false; status: number; body: Record<string, unknown>; reason?: DenialReason }>) | null,\n): Promise<GateResult> {\n if (!runGate || !enforcement) return { status: 'anonymous' };\n\n const outcome = await runGate();\n if (outcome.ok) return { status: 'verified' };\n\n // A sanctions deny stays terminal in BOTH modes — soft only downgrades non-sanctions reasons.\n if (enforcement === 'hard' || isSanctionsDenial(outcome.body)) {\n return {\n status: 'denied',\n denialStatus: outcome.status,\n denialBody: outcome.body,\n ...(outcome.reason !== undefined && { denialReason: outcome.reason }),\n };\n }\n return {\n status: 'unverified',\n denialStatus: outcome.status,\n denialBody: outcome.body,\n ...(outcome.reason !== undefined && { denialReason: outcome.reason }),\n };\n}\n\n/** NULL policy / NULL allowlist → ship anywhere. Otherwise country must be in the list. */\nexport function shippingCountryAllowed(country: string, policy: PolicyBlock | null | undefined): boolean {\n if (!policy?.allowedShippingCountries || policy.allowedShippingCountries.length === 0) return true;\n const allowed = new Set(policy.allowedShippingCountries.map((c) => c.toUpperCase()));\n return allowed.has(country.toUpperCase());\n}\n\n/**\n * US-state allowlist (e.g. wine).\n *\n * Only enforced for US shipments — non-US shipments are governed by\n * {@link shippingCountryAllowed} independently.\n */\nexport function shippingStateAllowed(\n state: string,\n country: string,\n policy: PolicyBlock | null | undefined,\n): boolean {\n if (!policy?.allowedShippingStates || policy.allowedShippingStates.length === 0) return true;\n if (country.toUpperCase() !== 'US') return true;\n const allowed = new Set(policy.allowedShippingStates.map((s) => s.toUpperCase()));\n return allowed.has(state.toUpperCase());\n}\n\n/**\n * Throw {@link CheckoutValidationError} when shipping isn't allowed by the policy.\n *\n * One-call replacement for the\n *\n * if (!shippingCountryAllowed(...)) throw new CheckoutValidationError(...);\n * if (!shippingStateAllowed(...)) throw new CheckoutValidationError(...);\n *\n * boilerplate every goods merchant writes in their `preValidate` hook.\n *\n * `policy` is a {@link PolicyBlock} (or `null`/`undefined`); NULL policy means\n * \"ship anywhere\" and the function is a no-op. The reason a location is\n * excluded is **merchant-defined**: it might be regulatory (regulated goods\n * + state allowlist), operational (no fulfillment partner), or commercial\n * (fragility, fraud-rate-by-region, etc.) — the helper doesn't assume.\n *\n * `productName` is the user-facing item name surfaced in the error message\n * (\"Cannot ship 'Wine 2020' to NY ...\"). Omit for a generic message.\n *\n * `errorCode` and `errorAction` let merchants override the canonical denial\n * codes if their consumer agents expect different shapes.\n *\n * `countryMessage` / `stateMessage` override the default messages verbatim\n * (use these when the default phrasing isn't right for your consumer agents\n * — e.g. you want to surface the regulatory reason explicitly, or you want\n * the message in a different language).\n */\nexport function validateShippingAgainstPolicy(opts: {\n country: string;\n state: string;\n policy: PolicyBlock | null | undefined;\n productName?: string;\n errorCode?: string;\n errorAction?: string;\n countryMessage?: string;\n stateMessage?: string;\n}): void {\n const code = opts.errorCode ?? 'unsupported_jurisdiction';\n const action = opts.errorAction ?? 'change_shipping_state';\n const item = opts.productName ? `'${opts.productName}'` : 'this item';\n if (!shippingCountryAllowed(opts.country, opts.policy)) {\n throw new CheckoutValidationError({\n code,\n message:\n opts.countryMessage ??\n `We can't ship ${item} to ${opts.country.toUpperCase() || '<unset>'}.`,\n action,\n });\n }\n if (!shippingStateAllowed(opts.state, opts.country, opts.policy)) {\n throw new CheckoutValidationError({\n code,\n message:\n opts.stateMessage ??\n `We can't ship ${item} to ${opts.state.toUpperCase() || '<unset>'}.`,\n action,\n });\n }\n}\n","/**\n * Cross-module typed errors.\n *\n * Lives in its own module so `payment/` and `stripe-multichain/` helpers can\n * throw `CheckoutValidationError` without importing from `checkout.ts` — this\n * also sidesteps tsup's per-entry class duplication.\n *\n * Re-exported from `checkout.ts` and the top-level entry to preserve the\n * public import path.\n */\n\n/**\n * Raised to short-circuit a Checkout flow with a 4xx/5xx envelope. The\n * framework catches this at request-flow boundaries (`preValidate`, recipient\n * minting, settlement dispatch) and emits `{ error, next_steps }` via\n * `buildValidationError` so merchants don't construct response bodies\n * themselves.\n */\nexport class CheckoutValidationError extends Error {\n readonly code: string;\n readonly action: string;\n readonly status: number;\n readonly extra: Record<string, unknown> | undefined;\n constructor(opts: {\n code: string;\n message: string;\n action?: string;\n status?: number;\n extra?: Record<string, unknown>;\n }) {\n super(opts.message);\n this.name = 'CheckoutValidationError';\n this.code = opts.code;\n this.action = opts.action ?? 'fix_request';\n this.status = opts.status ?? 400;\n this.extra = opts.extra;\n }\n}\n"],"mappings":"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","names":[]}
@@ -25,16 +25,35 @@ function buildGateFromPolicy(policy, base) {
25
25
  requireSanctionsClear: policy.requireSanctionsClear
26
26
  },
27
27
  ...policy.minAge !== void 0 && { minAge: policy.minAge },
28
+ ...policy.blockedJurisdictions !== void 0 && {
29
+ blockedJurisdictions: [...policy.blockedJurisdictions]
30
+ },
28
31
  ...policy.allowedJurisdictions !== void 0 && {
29
32
  allowedJurisdictions: [...policy.allowedJurisdictions]
30
33
  }
31
34
  };
32
35
  }
36
+ var SANCTIONS_DENIAL_REASONS = /* @__PURE__ */ new Set([
37
+ "sanctions_flagged",
38
+ "sanctions_check_unavailable"
39
+ ]);
40
+ function isSanctionsDenial(body) {
41
+ if (!body || typeof body !== "object") return false;
42
+ for (const key of ["reasons", "decision_reasons"]) {
43
+ const raw = body[key];
44
+ if (Array.isArray(raw) && raw.some((r) => typeof r === "string" && SANCTIONS_DENIAL_REASONS.has(r))) {
45
+ return true;
46
+ }
47
+ }
48
+ const error = body.error;
49
+ const code = error && typeof error === "object" ? error.code : void 0;
50
+ return typeof code === "string" && SANCTIONS_DENIAL_REASONS.has(code);
51
+ }
33
52
  async function runGateWithEnforcement(enforcement, runGate) {
34
53
  if (!runGate || !enforcement) return { status: "anonymous" };
35
54
  const outcome = await runGate();
36
55
  if (outcome.ok) return { status: "verified" };
37
- if (enforcement === "hard") {
56
+ if (enforcement === "hard" || isSanctionsDenial(outcome.body)) {
38
57
  return {
39
58
  status: "denied",
40
59
  denialStatus: outcome.status,
@@ -81,6 +100,7 @@ function validateShippingAgainstPolicy(opts) {
81
100
  }
82
101
  export {
83
102
  buildGateFromPolicy,
103
+ isSanctionsDenial,
84
104
  runGateWithEnforcement,
85
105
  shippingCountryAllowed,
86
106
  shippingStateAllowed,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/errors.ts","../../src/identity/policy.ts"],"sourcesContent":["/**\n * Cross-module typed errors.\n *\n * Lives in its own module so `payment/` and `stripe-multichain/` helpers can\n * throw `CheckoutValidationError` without importing from `checkout.ts` — this\n * also sidesteps tsup's per-entry class duplication.\n *\n * Re-exported from `checkout.ts` and the top-level entry to preserve the\n * public import path.\n */\n\n/**\n * Raised to short-circuit a Checkout flow with a 4xx/5xx envelope. The\n * framework catches this at request-flow boundaries (`preValidate`, recipient\n * minting, settlement dispatch) and emits `{ error, next_steps }` via\n * `buildValidationError` so merchants don't construct response bodies\n * themselves.\n */\nexport class CheckoutValidationError extends Error {\n readonly code: string;\n readonly action: string;\n readonly status: number;\n readonly extra: Record<string, unknown> | undefined;\n constructor(opts: {\n code: string;\n message: string;\n action?: string;\n status?: number;\n extra?: Record<string, unknown>;\n }) {\n super(opts.message);\n this.name = 'CheckoutValidationError';\n this.code = opts.code;\n this.action = opts.action ?? 'fix_request';\n this.status = opts.status ?? 400;\n this.extra = opts.extra;\n }\n}\n","/**\n * Per-product / per-tier compliance policy helpers.\n *\n * A *policy* is a small bag of fields describing what identity the merchant wants\n * verified for a given resource:\n *\n * - `enforcement`: `\"hard\"` (today's wine path — 403 on miss) or `\"soft\"` (gate\n * denial is swallowed; the order completes with a degraded `identity_status`).\n * `null` / absent = no gate at all.\n * - `requireKyc` / `requireSanctionsClear` / `minAge`: passed through to the\n * per-framework `agentscoreGate(...)` factory.\n * - `allowedJurisdictions`: buyer-verified country list (`[\"US\", \"CA\", ...]`).\n * - `allowedShippingCountries` / `allowedShippingStates`: optional shipping\n * allowlists. State list is only enforced for US shipments.\n *\n * This module ships three primitives:\n *\n * 1. {@link PolicyBlock} — the typed shape.\n * 2. {@link buildGateFromPolicy} — translate a block into the options object the\n * per-framework `agentscoreGate(...)` accepts. Returns `null` when the policy\n * has no enforcement (treat as \"no gate; anonymous OK\").\n * 3. {@link runGateWithEnforcement} — wrap a per-framework middleware in the\n * hard/soft enforcement runner. The middleware is given an `onDenied` shim\n * that captures the denial body and status; the runner returns a structured\n * {@link GateResult} so the vendor decides how to surface it.\n *\n * All three are additive — vendors using `agentscoreGate(...)` directly are\n * unaffected.\n */\n\nimport { CheckoutValidationError } from '../errors';\nimport type { AgentScoreCoreOptions, DenialReason } from '../core.js';\n\n/** Hard = 403 propagates; soft = swallowed + identity_status=\"unverified\". */\nexport type EnforcementMode = 'hard' | 'soft';\n\n/** Per-order trust level captured at settle time. */\nexport type IdentityStatus = 'verified' | 'unverified' | 'anonymous' | 'denied';\n\n/** Compliance fields a merchant attaches per product / per tier. All optional. */\nexport interface PolicyBlock {\n enforcement?: EnforcementMode;\n requireKyc?: boolean;\n requireSanctionsClear?: boolean;\n minAge?: number;\n allowedJurisdictions?: readonly string[];\n allowedShippingCountries?: readonly string[];\n allowedShippingStates?: readonly string[];\n}\n\n/**\n * Outcome of running a gate under an enforcement mode.\n *\n * - `verified`: gate accepted; identity is fully verified for the policy.\n * - `unverified`: soft mode swallowed a gate denial; the agent had *some*\n * identity but didn't meet the policy. Stamp this on the order so\n * ops/analytics can tell apart soft passes from hard passes.\n * - `anonymous`: no gate ran (policy was null / no enforcement).\n * - `denied`: hard mode rejected; the caller must propagate the 403. The\n * `denialBody` and `denialStatus` carry the original gate response so the\n * caller can return it as-is.\n */\nexport interface GateResult {\n status: IdentityStatus;\n denialStatus?: number;\n denialBody?: Record<string, unknown>;\n denialReason?: DenialReason;\n}\n\n/**\n * Translate a {@link PolicyBlock} into the options the per-framework\n * `agentscoreGate(...)` expects. Returns `null` when the block has no\n * `enforcement` set — the caller should treat that as \"no gate; anonymous OK\".\n *\n * Use a fresh gate per request rather than constructing once at module scope\n * when the policy varies per resource (e.g. per product). Each adapter's gate\n * is cheap to instantiate.\n */\nexport function buildGateFromPolicy(\n policy: PolicyBlock | null | undefined,\n base: { apiKey: string; baseUrl?: string },\n): AgentScoreCoreOptions | null {\n if (!policy || !policy.enforcement) return null;\n return {\n apiKey: base.apiKey,\n ...(base.baseUrl !== undefined && { baseUrl: base.baseUrl }),\n ...(policy.requireKyc !== undefined && { requireKyc: policy.requireKyc }),\n ...(policy.requireSanctionsClear !== undefined && {\n requireSanctionsClear: policy.requireSanctionsClear,\n }),\n ...(policy.minAge !== undefined && { minAge: policy.minAge }),\n ...(policy.allowedJurisdictions !== undefined && {\n allowedJurisdictions: [...policy.allowedJurisdictions],\n }),\n };\n}\n\n/**\n * Run a per-framework gate middleware respecting the enforcement mode.\n *\n * The vendor passes:\n * - `gate`: their framework's middleware (Hono `MiddlewareHandler`, Express\n * `(req, res, next) => void`, etc.) — anything that resolves on accept and\n * throws or returns a `Response` on deny.\n * - `runGate`: a thin adapter that calls the middleware with the framework\n * context and returns either `{ ok: true }` (gate accepted) or\n * `{ ok: false, status, body, reason? }` (gate denied with details).\n *\n * `runGateWithEnforcement` wraps that in the hard/soft split:\n *\n * - `gate=null` or `enforcement=null`: no gate fires; status=\"anonymous\".\n * - `enforcement=\"hard\"` + denied: status=\"denied\"; caller propagates denialStatus + denialBody.\n * - `enforcement=\"soft\"` + denied: swallow; status=\"unverified\".\n * - accepted: status=\"verified\".\n */\nexport async function runGateWithEnforcement(\n enforcement: EnforcementMode | undefined,\n runGate: (() => Promise<{ ok: true } | { ok: false; status: number; body: Record<string, unknown>; reason?: DenialReason }>) | null,\n): Promise<GateResult> {\n if (!runGate || !enforcement) return { status: 'anonymous' };\n\n const outcome = await runGate();\n if (outcome.ok) return { status: 'verified' };\n\n if (enforcement === 'hard') {\n return {\n status: 'denied',\n denialStatus: outcome.status,\n denialBody: outcome.body,\n ...(outcome.reason !== undefined && { denialReason: outcome.reason }),\n };\n }\n return {\n status: 'unverified',\n denialStatus: outcome.status,\n denialBody: outcome.body,\n ...(outcome.reason !== undefined && { denialReason: outcome.reason }),\n };\n}\n\n/** NULL policy / NULL allowlist → ship anywhere. Otherwise country must be in the list. */\nexport function shippingCountryAllowed(country: string, policy: PolicyBlock | null | undefined): boolean {\n if (!policy?.allowedShippingCountries || policy.allowedShippingCountries.length === 0) return true;\n const allowed = new Set(policy.allowedShippingCountries.map((c) => c.toUpperCase()));\n return allowed.has(country.toUpperCase());\n}\n\n/**\n * US-state allowlist (e.g. wine).\n *\n * Only enforced for US shipments — non-US shipments are governed by\n * {@link shippingCountryAllowed} independently.\n */\nexport function shippingStateAllowed(\n state: string,\n country: string,\n policy: PolicyBlock | null | undefined,\n): boolean {\n if (!policy?.allowedShippingStates || policy.allowedShippingStates.length === 0) return true;\n if (country.toUpperCase() !== 'US') return true;\n const allowed = new Set(policy.allowedShippingStates.map((s) => s.toUpperCase()));\n return allowed.has(state.toUpperCase());\n}\n\n/**\n * Throw {@link CheckoutValidationError} when shipping isn't allowed by the policy.\n *\n * One-call replacement for the\n *\n * if (!shippingCountryAllowed(...)) throw new CheckoutValidationError(...);\n * if (!shippingStateAllowed(...)) throw new CheckoutValidationError(...);\n *\n * boilerplate every goods merchant writes in their `preValidate` hook.\n *\n * `policy` is a {@link PolicyBlock} (or `null`/`undefined`); NULL policy means\n * \"ship anywhere\" and the function is a no-op. The reason a location is\n * excluded is **merchant-defined**: it might be regulatory (regulated goods\n * + state allowlist), operational (no fulfillment partner), or commercial\n * (fragility, fraud-rate-by-region, etc.) — the helper doesn't assume.\n *\n * `productName` is the user-facing item name surfaced in the error message\n * (\"Cannot ship 'Wine 2020' to NY ...\"). Omit for a generic message.\n *\n * `errorCode` and `errorAction` let merchants override the canonical denial\n * codes if their consumer agents expect different shapes.\n *\n * `countryMessage` / `stateMessage` override the default messages verbatim\n * (use these when the default phrasing isn't right for your consumer agents\n * — e.g. you want to surface the regulatory reason explicitly, or you want\n * the message in a different language).\n */\nexport function validateShippingAgainstPolicy(opts: {\n country: string;\n state: string;\n policy: PolicyBlock | null | undefined;\n productName?: string;\n errorCode?: string;\n errorAction?: string;\n countryMessage?: string;\n stateMessage?: string;\n}): void {\n const code = opts.errorCode ?? 'unsupported_jurisdiction';\n const action = opts.errorAction ?? 'change_shipping_state';\n const item = opts.productName ? `'${opts.productName}'` : 'this item';\n if (!shippingCountryAllowed(opts.country, opts.policy)) {\n throw new CheckoutValidationError({\n code,\n message:\n opts.countryMessage ??\n `We can't ship ${item} to ${opts.country.toUpperCase() || '<unset>'}.`,\n action,\n });\n }\n if (!shippingStateAllowed(opts.state, opts.country, opts.policy)) {\n throw new CheckoutValidationError({\n code,\n message:\n opts.stateMessage ??\n `We can't ship ${item} to ${opts.state.toUpperCase() || '<unset>'}.`,\n action,\n });\n }\n}\n"],"mappings":";AAkBO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAY,MAMT;AACD,UAAM,KAAK,OAAO;AAClB,SAAK,OAAO;AACZ,SAAK,OAAO,KAAK;AACjB,SAAK,SAAS,KAAK,UAAU;AAC7B,SAAK,SAAS,KAAK,UAAU;AAC7B,SAAK,QAAQ,KAAK;AAAA,EACpB;AACF;;;ACyCO,SAAS,oBACd,QACA,MAC8B;AAC9B,MAAI,CAAC,UAAU,CAAC,OAAO,YAAa,QAAO;AAC3C,SAAO;AAAA,IACL,QAAQ,KAAK;AAAA,IACb,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,IAC1D,GAAI,OAAO,eAAe,UAAa,EAAE,YAAY,OAAO,WAAW;AAAA,IACvE,GAAI,OAAO,0BAA0B,UAAa;AAAA,MAChD,uBAAuB,OAAO;AAAA,IAChC;AAAA,IACA,GAAI,OAAO,WAAW,UAAa,EAAE,QAAQ,OAAO,OAAO;AAAA,IAC3D,GAAI,OAAO,yBAAyB,UAAa;AAAA,MAC/C,sBAAsB,CAAC,GAAG,OAAO,oBAAoB;AAAA,IACvD;AAAA,EACF;AACF;AAoBA,eAAsB,uBACpB,aACA,SACqB;AACrB,MAAI,CAAC,WAAW,CAAC,YAAa,QAAO,EAAE,QAAQ,YAAY;AAE3D,QAAM,UAAU,MAAM,QAAQ;AAC9B,MAAI,QAAQ,GAAI,QAAO,EAAE,QAAQ,WAAW;AAE5C,MAAI,gBAAgB,QAAQ;AAC1B,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,cAAc,QAAQ;AAAA,MACtB,YAAY,QAAQ;AAAA,MACpB,GAAI,QAAQ,WAAW,UAAa,EAAE,cAAc,QAAQ,OAAO;AAAA,IACrE;AAAA,EACF;AACA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,cAAc,QAAQ;AAAA,IACtB,YAAY,QAAQ;AAAA,IACpB,GAAI,QAAQ,WAAW,UAAa,EAAE,cAAc,QAAQ,OAAO;AAAA,EACrE;AACF;AAGO,SAAS,uBAAuB,SAAiB,QAAiD;AACvG,MAAI,CAAC,QAAQ,4BAA4B,OAAO,yBAAyB,WAAW,EAAG,QAAO;AAC9F,QAAM,UAAU,IAAI,IAAI,OAAO,yBAAyB,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AACnF,SAAO,QAAQ,IAAI,QAAQ,YAAY,CAAC;AAC1C;AAQO,SAAS,qBACd,OACA,SACA,QACS;AACT,MAAI,CAAC,QAAQ,yBAAyB,OAAO,sBAAsB,WAAW,EAAG,QAAO;AACxF,MAAI,QAAQ,YAAY,MAAM,KAAM,QAAO;AAC3C,QAAM,UAAU,IAAI,IAAI,OAAO,sBAAsB,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AAChF,SAAO,QAAQ,IAAI,MAAM,YAAY,CAAC;AACxC;AA6BO,SAAS,8BAA8B,MASrC;AACP,QAAM,OAAO,KAAK,aAAa;AAC/B,QAAM,SAAS,KAAK,eAAe;AACnC,QAAM,OAAO,KAAK,cAAc,IAAI,KAAK,WAAW,MAAM;AAC1D,MAAI,CAAC,uBAAuB,KAAK,SAAS,KAAK,MAAM,GAAG;AACtD,UAAM,IAAI,wBAAwB;AAAA,MAChC;AAAA,MACA,SACE,KAAK,kBACL,iBAAiB,IAAI,OAAO,KAAK,QAAQ,YAAY,KAAK,SAAS;AAAA,MACrE;AAAA,IACF,CAAC;AAAA,EACH;AACA,MAAI,CAAC,qBAAqB,KAAK,OAAO,KAAK,SAAS,KAAK,MAAM,GAAG;AAChE,UAAM,IAAI,wBAAwB;AAAA,MAChC;AAAA,MACA,SACE,KAAK,gBACL,iBAAiB,IAAI,OAAO,KAAK,MAAM,YAAY,KAAK,SAAS;AAAA,MACnE;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/errors.ts","../../src/identity/policy.ts"],"sourcesContent":["/**\n * Cross-module typed errors.\n *\n * Lives in its own module so `payment/` and `stripe-multichain/` helpers can\n * throw `CheckoutValidationError` without importing from `checkout.ts` — this\n * also sidesteps tsup's per-entry class duplication.\n *\n * Re-exported from `checkout.ts` and the top-level entry to preserve the\n * public import path.\n */\n\n/**\n * Raised to short-circuit a Checkout flow with a 4xx/5xx envelope. The\n * framework catches this at request-flow boundaries (`preValidate`, recipient\n * minting, settlement dispatch) and emits `{ error, next_steps }` via\n * `buildValidationError` so merchants don't construct response bodies\n * themselves.\n */\nexport class CheckoutValidationError extends Error {\n readonly code: string;\n readonly action: string;\n readonly status: number;\n readonly extra: Record<string, unknown> | undefined;\n constructor(opts: {\n code: string;\n message: string;\n action?: string;\n status?: number;\n extra?: Record<string, unknown>;\n }) {\n super(opts.message);\n this.name = 'CheckoutValidationError';\n this.code = opts.code;\n this.action = opts.action ?? 'fix_request';\n this.status = opts.status ?? 400;\n this.extra = opts.extra;\n }\n}\n","/**\n * Per-product / per-tier compliance policy helpers.\n *\n * A *policy* is a small bag of fields describing what identity the merchant wants\n * verified for a given resource:\n *\n * - `enforcement`: `\"hard\"` (the regulated-goods path — 403 on miss) or `\"soft\"` (gate\n * denial is swallowed; the order completes with a degraded `identity_status`).\n * `null` / absent = no gate at all.\n * - `requireKyc` / `requireSanctionsClear` / `minAge`: passed through to the\n * per-framework `agentscoreGate(...)` factory.\n * - `allowedJurisdictions`: buyer-verified country list (`[\"US\", \"CA\", ...]`).\n * - `allowedShippingCountries` / `allowedShippingStates`: optional shipping\n * allowlists. State list is only enforced for US shipments.\n *\n * This module ships three primitives:\n *\n * 1. {@link PolicyBlock} — the typed shape.\n * 2. {@link buildGateFromPolicy} — translate a block into the options object the\n * per-framework `agentscoreGate(...)` accepts. Returns `null` when the policy\n * has no enforcement (treat as \"no gate; anonymous OK\").\n * 3. {@link runGateWithEnforcement} — wrap a per-framework middleware in the\n * hard/soft enforcement runner. The middleware is given an `onDenied` shim\n * that captures the denial body and status; the runner returns a structured\n * {@link GateResult} so the vendor decides how to surface it.\n *\n * All three are additive — vendors using `agentscoreGate(...)` directly are\n * unaffected.\n */\n\nimport { CheckoutValidationError } from '../errors';\nimport type { AgentScoreCoreOptions, DenialReason } from '../core.js';\n\n/** Hard = 403 propagates; soft = swallowed + identity_status=\"unverified\". */\nexport type EnforcementMode = 'hard' | 'soft';\n\n/** Per-order trust level captured at settle time. */\nexport type IdentityStatus = 'verified' | 'unverified' | 'anonymous' | 'denied';\n\n/** Compliance fields a merchant attaches per product / per tier. All optional. */\nexport interface PolicyBlock {\n enforcement?: EnforcementMode;\n requireKyc?: boolean;\n requireSanctionsClear?: boolean;\n minAge?: number;\n /** Buyer-verified country blocklist (`[\"RU\", \"KP\", ...]`) — these jurisdictions are denied. */\n blockedJurisdictions?: readonly string[];\n allowedJurisdictions?: readonly string[];\n allowedShippingCountries?: readonly string[];\n allowedShippingStates?: readonly string[];\n}\n\n/**\n * Outcome of running a gate under an enforcement mode.\n *\n * - `verified`: gate accepted; identity is fully verified for the policy.\n * - `unverified`: soft mode swallowed a gate denial; the agent had *some*\n * identity but didn't meet the policy. Stamp this on the order so\n * ops/analytics can tell apart soft passes from hard passes.\n * - `anonymous`: no gate ran (policy was null / no enforcement).\n * - `denied`: hard mode rejected; the caller must propagate the 403. The\n * `denialBody` and `denialStatus` carry the original gate response so the\n * caller can return it as-is.\n */\nexport interface GateResult {\n status: IdentityStatus;\n denialStatus?: number;\n denialBody?: Record<string, unknown>;\n denialReason?: DenialReason;\n}\n\n/**\n * Translate a {@link PolicyBlock} into the options the per-framework\n * `agentscoreGate(...)` expects. Returns `null` when the block has no\n * `enforcement` set — the caller should treat that as \"no gate; anonymous OK\".\n *\n * Use a fresh gate per request rather than constructing once at module scope\n * when the policy varies per resource (e.g. per product). Each adapter's gate\n * is cheap to instantiate.\n */\nexport function buildGateFromPolicy(\n policy: PolicyBlock | null | undefined,\n base: { apiKey: string; baseUrl?: string },\n): AgentScoreCoreOptions | null {\n if (!policy || !policy.enforcement) return null;\n return {\n apiKey: base.apiKey,\n ...(base.baseUrl !== undefined && { baseUrl: base.baseUrl }),\n ...(policy.requireKyc !== undefined && { requireKyc: policy.requireKyc }),\n ...(policy.requireSanctionsClear !== undefined && {\n requireSanctionsClear: policy.requireSanctionsClear,\n }),\n ...(policy.minAge !== undefined && { minAge: policy.minAge }),\n ...(policy.blockedJurisdictions !== undefined && {\n blockedJurisdictions: [...policy.blockedJurisdictions],\n }),\n ...(policy.allowedJurisdictions !== undefined && {\n allowedJurisdictions: [...policy.allowedJurisdictions],\n }),\n };\n}\n\n/**\n * OFAC SDN denial reasons. These are strict-liability: soft enforcement may\n * downgrade KYC / age / jurisdiction misses (the merchant accepts the order with a\n * degraded `identity_status`), but it must NEVER swallow a sanctions deny — falsely\n * settling for a sanctioned wallet is an OFAC violation regardless of the merchant's\n * soft posture. The API emits `sanctions_flagged` in `decision_reasons` for BOTH the\n * operator/wallet SDN hit and the payment-signer OFAC SDN hit;\n * `sanctions_check_unavailable` is the fail-closed unavailable-lookup variant (a\n * missing screen on a strict rail is also a hard deny). Match the canonical strings\n * from `the AgentScore API`.\n */\nconst SANCTIONS_DENIAL_REASONS: ReadonlySet<string> = new Set([\n 'sanctions_flagged',\n 'sanctions_check_unavailable',\n]);\n\n/**\n * True when a gate denial body indicates an OFAC SDN sanctions hit (or unavailable screen).\n *\n * Inspects the flat denial body emitted by `denialReasonToBody`: a `wallet_not_trusted`\n * (or signer-sanctions) deny carries the sanctions reason in `reasons` / `decision_reasons`,\n * or surfaces it as a top-level `error.code`. Used by {@link runGateWithEnforcement} so soft\n * mode can downgrade non-sanctions denials while leaving a sanctions deny terminal.\n */\nexport function isSanctionsDenial(body: Record<string, unknown> | null | undefined): boolean {\n if (!body || typeof body !== 'object') return false;\n for (const key of ['reasons', 'decision_reasons'] as const) {\n const raw = body[key];\n if (Array.isArray(raw) && raw.some((r) => typeof r === 'string' && SANCTIONS_DENIAL_REASONS.has(r))) {\n return true;\n }\n }\n // The signer-sanctions SDN deny may also surface as a top-level error code.\n const error = body.error;\n const code = error && typeof error === 'object' ? (error as Record<string, unknown>).code : undefined;\n return typeof code === 'string' && SANCTIONS_DENIAL_REASONS.has(code);\n}\n\n/**\n * Run a per-framework gate middleware respecting the enforcement mode.\n *\n * The vendor passes:\n * - `gate`: their framework's middleware (Hono `MiddlewareHandler`, Express\n * `(req, res, next) => void`, etc.) — anything that resolves on accept and\n * throws or returns a `Response` on deny.\n * - `runGate`: a thin adapter that calls the middleware with the framework\n * context and returns either `{ ok: true }` (gate accepted) or\n * `{ ok: false, status, body, reason? }` (gate denied with details).\n *\n * `runGateWithEnforcement` wraps that in the hard/soft split:\n *\n * - `gate=null` or `enforcement=null`: no gate fires; status=\"anonymous\".\n * - `enforcement=\"hard\"` + denied: status=\"denied\"; caller propagates denialStatus + denialBody.\n * - `enforcement=\"soft\"` + denied: swallow; status=\"unverified\".\n * - accepted: status=\"verified\".\n *\n * **Sanctions are never swallowed.** Soft mode is a commercial knob — it lets a merchant\n * accept an order from an agent that didn't satisfy KYC / age / jurisdiction (stamping a\n * degraded `identity_status` for ops). But an OFAC SDN sanctions deny is strict-liability:\n * settling for a sanctioned wallet is a violation regardless of the merchant's posture. So a\n * denial whose body indicates sanctions ({@link isSanctionsDenial}) returns `status=\"denied\"`\n * even under `enforcement=\"soft\"`; soft only downgrades the non-sanctions reasons.\n */\nexport async function runGateWithEnforcement(\n enforcement: EnforcementMode | undefined,\n runGate: (() => Promise<{ ok: true } | { ok: false; status: number; body: Record<string, unknown>; reason?: DenialReason }>) | null,\n): Promise<GateResult> {\n if (!runGate || !enforcement) return { status: 'anonymous' };\n\n const outcome = await runGate();\n if (outcome.ok) return { status: 'verified' };\n\n // A sanctions deny stays terminal in BOTH modes — soft only downgrades non-sanctions reasons.\n if (enforcement === 'hard' || isSanctionsDenial(outcome.body)) {\n return {\n status: 'denied',\n denialStatus: outcome.status,\n denialBody: outcome.body,\n ...(outcome.reason !== undefined && { denialReason: outcome.reason }),\n };\n }\n return {\n status: 'unverified',\n denialStatus: outcome.status,\n denialBody: outcome.body,\n ...(outcome.reason !== undefined && { denialReason: outcome.reason }),\n };\n}\n\n/** NULL policy / NULL allowlist → ship anywhere. Otherwise country must be in the list. */\nexport function shippingCountryAllowed(country: string, policy: PolicyBlock | null | undefined): boolean {\n if (!policy?.allowedShippingCountries || policy.allowedShippingCountries.length === 0) return true;\n const allowed = new Set(policy.allowedShippingCountries.map((c) => c.toUpperCase()));\n return allowed.has(country.toUpperCase());\n}\n\n/**\n * US-state allowlist (e.g. wine).\n *\n * Only enforced for US shipments — non-US shipments are governed by\n * {@link shippingCountryAllowed} independently.\n */\nexport function shippingStateAllowed(\n state: string,\n country: string,\n policy: PolicyBlock | null | undefined,\n): boolean {\n if (!policy?.allowedShippingStates || policy.allowedShippingStates.length === 0) return true;\n if (country.toUpperCase() !== 'US') return true;\n const allowed = new Set(policy.allowedShippingStates.map((s) => s.toUpperCase()));\n return allowed.has(state.toUpperCase());\n}\n\n/**\n * Throw {@link CheckoutValidationError} when shipping isn't allowed by the policy.\n *\n * One-call replacement for the\n *\n * if (!shippingCountryAllowed(...)) throw new CheckoutValidationError(...);\n * if (!shippingStateAllowed(...)) throw new CheckoutValidationError(...);\n *\n * boilerplate every goods merchant writes in their `preValidate` hook.\n *\n * `policy` is a {@link PolicyBlock} (or `null`/`undefined`); NULL policy means\n * \"ship anywhere\" and the function is a no-op. The reason a location is\n * excluded is **merchant-defined**: it might be regulatory (regulated goods\n * + state allowlist), operational (no fulfillment partner), or commercial\n * (fragility, fraud-rate-by-region, etc.) — the helper doesn't assume.\n *\n * `productName` is the user-facing item name surfaced in the error message\n * (\"Cannot ship 'Wine 2020' to NY ...\"). Omit for a generic message.\n *\n * `errorCode` and `errorAction` let merchants override the canonical denial\n * codes if their consumer agents expect different shapes.\n *\n * `countryMessage` / `stateMessage` override the default messages verbatim\n * (use these when the default phrasing isn't right for your consumer agents\n * — e.g. you want to surface the regulatory reason explicitly, or you want\n * the message in a different language).\n */\nexport function validateShippingAgainstPolicy(opts: {\n country: string;\n state: string;\n policy: PolicyBlock | null | undefined;\n productName?: string;\n errorCode?: string;\n errorAction?: string;\n countryMessage?: string;\n stateMessage?: string;\n}): void {\n const code = opts.errorCode ?? 'unsupported_jurisdiction';\n const action = opts.errorAction ?? 'change_shipping_state';\n const item = opts.productName ? `'${opts.productName}'` : 'this item';\n if (!shippingCountryAllowed(opts.country, opts.policy)) {\n throw new CheckoutValidationError({\n code,\n message:\n opts.countryMessage ??\n `We can't ship ${item} to ${opts.country.toUpperCase() || '<unset>'}.`,\n action,\n });\n }\n if (!shippingStateAllowed(opts.state, opts.country, opts.policy)) {\n throw new CheckoutValidationError({\n code,\n message:\n opts.stateMessage ??\n `We can't ship ${item} to ${opts.state.toUpperCase() || '<unset>'}.`,\n action,\n });\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,8 @@
1
+ import { d as AipGateOptions, o as buildAipErrorBody, V as VerifiedAit } from '../gate-GCXy4FHI.mjs';
1
2
  import { AgentScoreCoreOptions, AgentIdentity, DenialReason, CreateSessionOnMissing, AssessResult, SignerVerdict, FailOpenInfraReason, GateQuotaInfo } from '../core.mjs';
3
+ import '../types-D1slMt0H.mjs';
4
+ import 'jose';
5
+ import '@agent-score/sdk';
2
6
  import '../signer-3FAit11j.mjs';
3
7
 
4
8
  interface AgentScoreGateOptions extends Omit<AgentScoreCoreOptions, 'createSessionOnMissing'> {
@@ -96,5 +100,26 @@ declare function createConditionalAgentScoreGate(options: AgentScoreGateOptions)
96
100
  /** Wrapper variant matching `withAgentScoreGate(opts, handler)` that only
97
101
  * invokes the gate when a payment credential is attached. */
98
102
  declare function withConditionalAgentScoreGate<TCtx = unknown>(options: AgentScoreGateOptions, handler: Parameters<typeof withAgentScoreGate<TCtx>>[1]): (req: Request, ctx: TCtx) => Promise<Response>;
103
+ type AipGuardResult = {
104
+ allowed: true;
105
+ ait: VerifiedAit;
106
+ } | {
107
+ allowed: false;
108
+ response: Response;
109
+ };
110
+ interface AipGateWebOptions extends AipGateOptions {
111
+ /** Custom denial responder. Defaults to a 401/403 `application/problem+json` Response. */
112
+ onDenied?: (req: Request, body: ReturnType<typeof buildAipErrorBody>) => Response | Promise<Response>;
113
+ }
114
+ /** Create a Web Fetch AIP guard: returns `{ allowed, ait }` or `{ allowed: false, response }`. */
115
+ declare function createAipGate(options: AipGateWebOptions): (req: Request) => Promise<AipGuardResult>;
116
+ /** Wrap a Web Fetch handler with the AIP gate. Denied requests return the problem+json Response. */
117
+ declare function withAipGate<TCtx = unknown>(options: AipGateWebOptions, handler: (req: Request, gate: {
118
+ ait: VerifiedAit;
119
+ }, ctx?: TCtx) => Response | Promise<Response>): (req: Request, ctx?: TCtx) => Promise<Response>;
120
+ /** Conditional variant: only runs the AIP gate when an `Agent-Identity` header is present. */
121
+ declare function withConditionalAipGate<TCtx = unknown>(options: AipGateWebOptions, handler: (req: Request, gate: {
122
+ ait?: VerifiedAit;
123
+ }, ctx?: TCtx) => Response | Promise<Response>): (req: Request, ctx?: TCtx) => Promise<Response>;
99
124
 
100
- export { type GuardResult, createAgentScoreGate, createConditionalAgentScoreGate, withAgentScoreGate, withConditionalAgentScoreGate };
125
+ export { type AipGateWebOptions, type AipGuardResult, type GuardResult, createAgentScoreGate, createAipGate, createConditionalAgentScoreGate, withAgentScoreGate, withAipGate, withConditionalAgentScoreGate, withConditionalAipGate };
@@ -1,4 +1,8 @@
1
+ import { d as AipGateOptions, o as buildAipErrorBody, V as VerifiedAit } from '../gate-D2RP6bZg.js';
1
2
  import { AgentScoreCoreOptions, AgentIdentity, DenialReason, CreateSessionOnMissing, AssessResult, SignerVerdict, FailOpenInfraReason, GateQuotaInfo } from '../core.js';
3
+ import '../types-D1slMt0H.js';
4
+ import 'jose';
5
+ import '@agent-score/sdk';
2
6
  import '../signer-3FAit11j.js';
3
7
 
4
8
  interface AgentScoreGateOptions extends Omit<AgentScoreCoreOptions, 'createSessionOnMissing'> {
@@ -96,5 +100,26 @@ declare function createConditionalAgentScoreGate(options: AgentScoreGateOptions)
96
100
  /** Wrapper variant matching `withAgentScoreGate(opts, handler)` that only
97
101
  * invokes the gate when a payment credential is attached. */
98
102
  declare function withConditionalAgentScoreGate<TCtx = unknown>(options: AgentScoreGateOptions, handler: Parameters<typeof withAgentScoreGate<TCtx>>[1]): (req: Request, ctx: TCtx) => Promise<Response>;
103
+ type AipGuardResult = {
104
+ allowed: true;
105
+ ait: VerifiedAit;
106
+ } | {
107
+ allowed: false;
108
+ response: Response;
109
+ };
110
+ interface AipGateWebOptions extends AipGateOptions {
111
+ /** Custom denial responder. Defaults to a 401/403 `application/problem+json` Response. */
112
+ onDenied?: (req: Request, body: ReturnType<typeof buildAipErrorBody>) => Response | Promise<Response>;
113
+ }
114
+ /** Create a Web Fetch AIP guard: returns `{ allowed, ait }` or `{ allowed: false, response }`. */
115
+ declare function createAipGate(options: AipGateWebOptions): (req: Request) => Promise<AipGuardResult>;
116
+ /** Wrap a Web Fetch handler with the AIP gate. Denied requests return the problem+json Response. */
117
+ declare function withAipGate<TCtx = unknown>(options: AipGateWebOptions, handler: (req: Request, gate: {
118
+ ait: VerifiedAit;
119
+ }, ctx?: TCtx) => Response | Promise<Response>): (req: Request, ctx?: TCtx) => Promise<Response>;
120
+ /** Conditional variant: only runs the AIP gate when an `Agent-Identity` header is present. */
121
+ declare function withConditionalAipGate<TCtx = unknown>(options: AipGateWebOptions, handler: (req: Request, gate: {
122
+ ait?: VerifiedAit;
123
+ }, ctx?: TCtx) => Response | Promise<Response>): (req: Request, ctx?: TCtx) => Promise<Response>;
99
124
 
100
- export { type GuardResult, createAgentScoreGate, createConditionalAgentScoreGate, withAgentScoreGate, withConditionalAgentScoreGate };
125
+ export { type AipGateWebOptions, type AipGuardResult, type GuardResult, createAgentScoreGate, createAipGate, createConditionalAgentScoreGate, withAgentScoreGate, withAipGate, withConditionalAgentScoreGate, withConditionalAipGate };