@goodmeta/agent-verifier 0.2.0 → 0.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 (78) hide show
  1. package/README.md +67 -26
  2. package/dist/ap2/chain.d.ts +43 -0
  3. package/dist/ap2/chain.d.ts.map +1 -0
  4. package/dist/ap2/chain.js +100 -0
  5. package/dist/ap2/chain.js.map +1 -0
  6. package/dist/ap2/chains.d.ts +68 -0
  7. package/dist/ap2/chains.d.ts.map +1 -0
  8. package/dist/ap2/chains.js +119 -0
  9. package/dist/ap2/chains.js.map +1 -0
  10. package/dist/ap2/constraints.d.ts +47 -0
  11. package/dist/ap2/constraints.d.ts.map +1 -0
  12. package/dist/ap2/constraints.js +231 -0
  13. package/dist/ap2/constraints.js.map +1 -0
  14. package/dist/ap2/hash.d.ts +23 -0
  15. package/dist/ap2/hash.d.ts.map +1 -0
  16. package/dist/ap2/hash.js +54 -0
  17. package/dist/ap2/hash.js.map +1 -0
  18. package/dist/ap2/index.d.ts +28 -0
  19. package/dist/ap2/index.d.ts.map +1 -0
  20. package/dist/ap2/index.js +35 -0
  21. package/dist/ap2/index.js.map +1 -0
  22. package/dist/ap2/jwk.d.ts +24 -0
  23. package/dist/ap2/jwk.d.ts.map +1 -0
  24. package/dist/ap2/jwk.js +29 -0
  25. package/dist/ap2/jwk.js.map +1 -0
  26. package/dist/ap2/kb-sd-jwt.d.ts +44 -0
  27. package/dist/ap2/kb-sd-jwt.d.ts.map +1 -0
  28. package/dist/ap2/kb-sd-jwt.js +97 -0
  29. package/dist/ap2/kb-sd-jwt.js.map +1 -0
  30. package/dist/ap2/keys.d.ts +41 -0
  31. package/dist/ap2/keys.d.ts.map +1 -0
  32. package/dist/ap2/keys.js +114 -0
  33. package/dist/ap2/keys.js.map +1 -0
  34. package/dist/ap2/max-flow.d.ts +26 -0
  35. package/dist/ap2/max-flow.d.ts.map +1 -0
  36. package/dist/ap2/max-flow.js +173 -0
  37. package/dist/ap2/max-flow.js.map +1 -0
  38. package/dist/ap2/parse.d.ts +35 -0
  39. package/dist/ap2/parse.d.ts.map +1 -0
  40. package/dist/ap2/parse.js +78 -0
  41. package/dist/ap2/parse.js.map +1 -0
  42. package/dist/ap2/sd-jwt.d.ts +83 -0
  43. package/dist/ap2/sd-jwt.d.ts.map +1 -0
  44. package/dist/ap2/sd-jwt.js +306 -0
  45. package/dist/ap2/sd-jwt.js.map +1 -0
  46. package/dist/ap2/types.d.ts +270 -0
  47. package/dist/ap2/types.d.ts.map +1 -0
  48. package/dist/ap2/types.js +141 -0
  49. package/dist/ap2/types.js.map +1 -0
  50. package/dist/client.d.ts +152 -0
  51. package/dist/client.d.ts.map +1 -0
  52. package/dist/client.js +171 -0
  53. package/dist/client.js.map +1 -0
  54. package/dist/index.d.ts +27 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +30 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/policy.d.ts +45 -0
  59. package/dist/policy.d.ts.map +1 -0
  60. package/dist/policy.js +88 -0
  61. package/dist/policy.js.map +1 -0
  62. package/dist/receipt-jwt.d.ts +30 -0
  63. package/dist/receipt-jwt.d.ts.map +1 -0
  64. package/dist/receipt-jwt.js +39 -0
  65. package/dist/receipt-jwt.js.map +1 -0
  66. package/dist/schema.d.ts +41 -0
  67. package/dist/schema.d.ts.map +1 -0
  68. package/dist/schema.js +50 -0
  69. package/dist/schema.js.map +1 -0
  70. package/dist/types.d.ts +116 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +8 -0
  73. package/dist/types.js.map +1 -0
  74. package/dist/verify.d.ts +24 -0
  75. package/dist/verify.d.ts.map +1 -0
  76. package/dist/verify.js +69 -0
  77. package/dist/verify.js.map +1 -0
  78. package/package.json +14 -7
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @goodmeta/agent-verifier
2
2
 
3
- Can this agent spend $X on Y right now? One function call.
3
+ Implements the [Budget Authority Protocol](https://github.com/goodmeta/agent-payments-landscape/blob/main/specs/budget-authority-protocol.md). Can this agent spend $X on Y right now? One function call.
4
4
 
5
5
  ```ts
6
6
  import { checkPolicy } from "@goodmeta/agent-verifier"
@@ -19,7 +19,7 @@ Or with the hosted verifier (cross-agent budget tracking):
19
19
  ```ts
20
20
  import { VerifierClient } from "@goodmeta/agent-verifier"
21
21
 
22
- const verifier = new VerifierClient({ apiKey: "gm_live_..." })
22
+ const verifier = new VerifierClient({ apiKey: "gm_test_..." })
23
23
  const { approved, verificationId } = await verifier.verify(mandate, {
24
24
  amount: "3000",
25
25
  idempotencyKey: "tx-1",
@@ -88,26 +88,52 @@ checkPolicy(policy, {
88
88
  // → { approved: false, reason: "CODE_NOT_ALLOWED", detail: 'Code "storage" not in allowed list' }
89
89
  ```
90
90
 
91
- ### AP2 mandate verification
91
+ ### Real AP2 mandate verification (dSD-JWT)
92
92
 
93
- For agents carrying [AP2](https://ap2-protocol.org/) cryptographic mandates (Google, 60+ partners). Verifies EIP-712 signatures and checks constraints.
93
+ [AP2](https://ap2-protocol.org/) mandates (Google, 60+ partners) are **not** plain signed JSON — they are **dSD-JWT delegation chains**: an issuer-signed SD-JWT root plus N KB-SD-JWT hops, each hop signed by the previous hop's `cnf.jwk`, hash-chained via `sd_hash`/`issuer_jwt_hash`, ES256/P-256. The `ap2` namespace verifies the whole chain and evaluates the mandate's constraints. It is **ported byte-exact from AP2's reference SDK** (pinned commit, 65 golden vectors) and is **stricter than AP2 on every trust decision** — see [`AP2-AUDIT.md`](./AP2-AUDIT.md).
94
94
 
95
95
  ```ts
96
- import {
97
- verifyIntentSignature,
98
- checkConstraints,
99
- } from "@goodmeta/agent-verifier"
96
+ import { ap2 } from "@goodmeta/agent-verifier"
97
+
98
+ // 1. Verify the delegation chain. Pass the root issuer key (or an x5c/kid
99
+ // provider) and the expected audience + nonce — both REQUIRED (fail-closed).
100
+ const tokens = ap2.splitChain(compactChain)
101
+ const payloads = await ap2.verifyChain(tokens, rootIssuerJwk, {
102
+ expectedAud: "merchant",
103
+ expectedNonce: "nonce-1",
104
+ })
105
+ // signatures, cnf hop-chaining, sd_hash binding, typ rules, aud/nonce all
106
+ // verified — throws on any failure. payloads = [open, ...intermediate, closed]
107
+
108
+ // 2. Type the [open, closed] pair and evaluate constraints (budget, amount
109
+ // range, allowed payees/instruments, line-items max-flow, …).
110
+ const chain = ap2.parsePaymentChain(payloads)
111
+ const violations = ap2.verifyPaymentChain(chain, {
112
+ mandateContext: { total_amount: 0, total_uses: 0 },
113
+ })
114
+ if (violations.length) throw new Error(violations.join("; ")) // [] = authorized
100
115
 
101
- // verify the mandate signature is real
102
- const sig = await verifyIntentSignature(mandate)
103
- if (!sig.valid) throw new Error(sig.error)
116
+ // 3. Stable receipt reference (hash of the final SD-JWT in the chain).
117
+ const reference = ap2.receiptReference(compactChain)
118
+ ```
104
119
 
105
- // check spending constraints
106
- const check = checkConstraints(mandate, {
107
- amount: "3000",
108
- merchantId: "merchant-1",
109
- })
110
- if (!check.valid) throw new Error(check.error)
120
+ For certificate-anchored roots, build a fail-closed `x5c`/`kid` provider (mandatory trusted roots, validity window, `CA:TRUE`, leaf P-256, anchor-to-root):
121
+
122
+ ```ts
123
+ const provider = ap2.x5cOrKidProvider({ trustedRoots /* X509Certificate[] */ })
124
+ const payloads = await ap2.verifyChain(tokens, provider, { expectedAud, expectedNonce })
125
+ ```
126
+
127
+ ### Receipts (ES256 JWS)
128
+
129
+ A *Mandate Receipt* in AP2 is a Verifier-signed JWT (`iss`/`result`/`reference`). `signReceipt`/`verifyReceipt` are a plain compact ES256 JWS over the whole payload — the receipt mechanism. (They also back the simpler legacy `IntentMandate` self-hosted flow; see `examples/single-merchant.ts`.)
130
+
131
+ ```ts
132
+ import { signReceipt, verifyReceipt } from "@goodmeta/agent-verifier"
133
+
134
+ const token = await signReceipt(receipt, verifierPrivateJwk)
135
+ const { valid, payload, error } = await verifyReceipt(token, verifierPublicJwk)
136
+ if (!valid) throw new Error(error)
111
137
  ```
112
138
 
113
139
  ### Hosted verifier
@@ -118,7 +144,7 @@ When one agent's budget spans multiple services — Mistral AND Groq AND CoreWea
118
144
  import { VerifierClient } from "@goodmeta/agent-verifier"
119
145
 
120
146
  const verifier = new VerifierClient({
121
- apiKey: "gm_live_...",
147
+ apiKey: "gm_test_...",
122
148
  baseUrl: "https://verifier.goodmeta.co",
123
149
  })
124
150
 
@@ -149,16 +175,26 @@ if (approved) {
149
175
  | --- | --- |
150
176
  | `checkPolicy(policy, request, currentSpend?)` | Check a spending request against policy constraints |
151
177
 
152
- ### AP2 mandates
178
+ ### AP2 dSD-JWT verifier (`ap2.*`)
153
179
 
154
180
  | Function | Description |
155
181
  | --- | --- |
156
- | `verifyIntentSignature(mandate)` | Verify EIP-712 signature on an Intent Mandate |
157
- | `verifyCartSignature(mandate)` | Verify EIP-712 signature on a Cart Mandate |
158
- | `checkConstraints(mandate, tx)` | Check budget, merchant, category, and temporal constraints |
159
- | `signIntentMandate(mandate, account)` | User signs autonomous spending authority |
160
- | `signCartMandate(mandate, account)` | Merchant signs price commitment |
161
- | `approveCartMandate(mandate, account)` | User approves a specific purchase |
182
+ | `ap2.splitChain(compact)` | Split a `~~`-joined dSD-JWT chain into parsed tokens |
183
+ | `ap2.verifyChain(tokens, keyOrProvider, opts)` | Verify the full delegation chain (signatures, cnf hop-chaining, binding, typ, terminal aud/nonce); returns per-hop payloads, throws on failure |
184
+ | `ap2.x5cOrKidProvider(opts)` | Build a fail-closed root-key provider from `x5c` certs or a `kid` lookup |
185
+ | `ap2.parsePaymentChain` / `ap2.verifyPaymentChain` | Type the `[open, closed]` payment pair; evaluate constraints + linkage (`[]` = ok) |
186
+ | `ap2.parseCheckoutChain` / `ap2.verifyCheckoutChain` | Same for checkout; self-computes `checkout_hash` from the embedded `checkout_jwt` |
187
+ | `ap2.checkPaymentConstraints` / `ap2.checkCheckoutConstraints` | Evaluate the 8 payment / 2 checkout constraints directly |
188
+ | `ap2.receiptReference(compact)` | Stable receipt reference (`sd_hash` of the final SD-JWT) |
189
+ | `ap2.*Schema` / types | zod schemas + types for the snake_case AP2 mandates |
190
+
191
+ ### Receipts (ES256 JWS)
192
+
193
+ | Function | Description |
194
+ | --- | --- |
195
+ | `signReceipt(payload, privateJwk)` | Sign a payload as a compact ES256 JWS (AP2 receipt format) |
196
+ | `verifyReceipt(token, publicJwk)` | Verify the JWS signature; returns the trusted payload |
197
+ | `checkConstraints(mandate, tx)` | (Legacy) check budget/merchant/category/temporal constraints on an `IntentMandate` |
162
198
 
163
199
  ### Hosted verifier client
164
200
 
@@ -166,9 +202,14 @@ if (approved) {
166
202
  | --- | --- |
167
203
  | `verifier.verify(mandate, tx)` | Verify with full mandate object + place budget hold |
168
204
  | `verifier.verifyById(mandateId, tx)` | Verify by ID (agent passes ID, verifier has mandate on file) |
169
- | `verifier.settle(id, result)` | Confirm payment or release hold |
205
+ | `verifier.createBudget(opts)` | Create a budget envelope no signature; the API key is the trust anchor |
206
+ | `verifier.settle(id, result)` | Confirm payment (debit budget) or release the hold |
207
+ | `verifier.release(id)` | Return a pre-commit hold to the budget (before settlement) |
208
+ | `verifier.refund(id, cents, key)` | Reverse a settled payment, full or partial (idempotent) |
170
209
  | `verifier.getMandateState(id)` | Query budget, tx count, and history |
171
210
 
211
+ Calls apply a timeout and throw a typed `VerifierError` (with HTTP `status` + `body`) on transport, auth, or server failures; a denied verification is returned with `approved: false`, not thrown.
212
+
172
213
  ## Related
173
214
 
174
215
  - [AP2](https://ap2-protocol.org/) — Agent payment authorization by Google (60+ partners)
@@ -0,0 +1,43 @@
1
+ /**
2
+ * dSD-JWT delegation-chain walk.
3
+ *
4
+ * Port of AP2's `sdjwt/chain.py::verify_chain` (+ `_check_time_claims`):
5
+ * verify the root SD-JWT, then each KB-SD-JWT hop under the previous hop's
6
+ * `cnf.jwk`, returning one effective payload per hop (`[open, …, closed]`).
7
+ *
8
+ * Hardenings over AP2's reference (PLAN §5):
9
+ * H4 — `expectedAud`+`expectedNonce` are REQUIRED for any chain that has KB
10
+ * hops (AP2 leaves them optional); fail closed if absent.
11
+ * H5 — DoS caps enforced BEFORE any crypto (chain depth, per-token bytes,
12
+ * disclosures per token).
13
+ * The signature/binding/typ/cnf checks live in the per-primitive modules
14
+ * (`sd-jwt.ts`, `kb-sd-jwt.ts`), as in AP2.
15
+ */
16
+ import type { ParsedToken } from "./parse.js";
17
+ import { type VerificationKey } from "./sd-jwt.js";
18
+ type Json = Record<string, unknown>;
19
+ /** DoS caps (H5) — frozen so a consumer of the public barrel can't weaken them
20
+ * process-wide (`as const` is compile-time only). */
21
+ export declare const CHAIN_CAPS: Readonly<{
22
+ maxDepth: 8;
23
+ maxTokenBytes: number;
24
+ maxDisclosuresPerToken: 64;
25
+ }>;
26
+ /** Resolves the root token's verification key (x5c/kid trust → P4). */
27
+ export type RootKeyProvider = (root: ParsedToken) => VerificationKey;
28
+ export interface VerifyChainOptions {
29
+ expectedAud?: string;
30
+ expectedNonce?: string;
31
+ clockSkewSeconds?: number;
32
+ /** Unix seconds; defaults to the current time. Override for reproducible tests. */
33
+ currentTime?: number;
34
+ }
35
+ /**
36
+ * Verify a dSD-JWT delegation chain and return per-hop effective payloads.
37
+ *
38
+ * @param tokens parsed chain segments (root first), e.g. from `splitChain`.
39
+ * @param rootKey the root issuer key, or a provider `(root) => key` (P4 x5c/kid).
40
+ */
41
+ export declare function verifyChain(tokens: ParsedToken[], rootKey: VerificationKey | RootKeyProvider, opts?: VerifyChainOptions): Promise<Json[]>;
42
+ export {};
43
+ //# sourceMappingURL=chain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chain.d.ts","sourceRoot":"","sources":["../../src/ap2/chain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAA6D,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9G,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpC;qDACqD;AACrD,eAAO,MAAM,UAAU;;;;EAIrB,CAAC;AAIH,uEAAuE;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,WAAW,KAAK,eAAe,CAAC;AAErE,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgCD;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EAAE,EACrB,OAAO,EAAE,eAAe,GAAG,eAAe,EAC1C,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,IAAI,EAAE,CAAC,CAuCjB"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * dSD-JWT delegation-chain walk.
3
+ *
4
+ * Port of AP2's `sdjwt/chain.py::verify_chain` (+ `_check_time_claims`):
5
+ * verify the root SD-JWT, then each KB-SD-JWT hop under the previous hop's
6
+ * `cnf.jwk`, returning one effective payload per hop (`[open, …, closed]`).
7
+ *
8
+ * Hardenings over AP2's reference (PLAN §5):
9
+ * H4 — `expectedAud`+`expectedNonce` are REQUIRED for any chain that has KB
10
+ * hops (AP2 leaves them optional); fail closed if absent.
11
+ * H5 — DoS caps enforced BEFORE any crypto (chain depth, per-token bytes,
12
+ * disclosures per token).
13
+ * The signature/binding/typ/cnf checks live in the per-primitive modules
14
+ * (`sd-jwt.ts`, `kb-sd-jwt.ts`), as in AP2.
15
+ */
16
+ import { verifyRootSdJwt, resolveDelegateItems } from "./sd-jwt.js";
17
+ import { verifyKbHop } from "./kb-sd-jwt.js";
18
+ /** DoS caps (H5) — frozen so a consumer of the public barrel can't weaken them
19
+ * process-wide (`as const` is compile-time only). */
20
+ export const CHAIN_CAPS = Object.freeze({
21
+ maxDepth: 8,
22
+ maxTokenBytes: 64 * 1024,
23
+ maxDisclosuresPerToken: 64,
24
+ });
25
+ const DEFAULT_CLOCK_SKEW_SECONDS = 300;
26
+ function enforceCaps(tokens) {
27
+ if (tokens.length > CHAIN_CAPS.maxDepth) {
28
+ throw new Error(`Chain too deep: ${tokens.length} tokens > cap ${CHAIN_CAPS.maxDepth}`);
29
+ }
30
+ tokens.forEach((t, i) => {
31
+ if (t.canonical.length > CHAIN_CAPS.maxTokenBytes) {
32
+ throw new Error(`Token ${i} exceeds ${CHAIN_CAPS.maxTokenBytes} bytes`);
33
+ }
34
+ if (t.disclosures.length > CHAIN_CAPS.maxDisclosuresPerToken) {
35
+ throw new Error(`Token ${i} has ${t.disclosures.length} disclosures > cap ${CHAIN_CAPS.maxDisclosuresPerToken}`);
36
+ }
37
+ });
38
+ }
39
+ /** Port of `_check_time_claims`: reject expired tokens and future-dated `iat`. */
40
+ function checkTimeClaims(payloads, tokenIndex, now, skew) {
41
+ for (const p of payloads) {
42
+ const exp = p["exp"];
43
+ if (exp !== undefined && exp !== null) {
44
+ if (typeof exp !== "number")
45
+ throw new Error(`Token ${tokenIndex} has invalid 'exp' claim type`);
46
+ if (now > exp + skew)
47
+ throw new Error(`Token ${tokenIndex} expired at ${exp}`);
48
+ }
49
+ const iat = p["iat"];
50
+ if (iat !== undefined && iat !== null) {
51
+ if (typeof iat !== "number")
52
+ throw new Error(`Token ${tokenIndex} has invalid 'iat' claim type`);
53
+ if (iat > now + skew)
54
+ throw new Error(`Token ${tokenIndex} iat is in the future: ${iat}`);
55
+ }
56
+ }
57
+ }
58
+ /**
59
+ * Verify a dSD-JWT delegation chain and return per-hop effective payloads.
60
+ *
61
+ * @param tokens parsed chain segments (root first), e.g. from `splitChain`.
62
+ * @param rootKey the root issuer key, or a provider `(root) => key` (P4 x5c/kid).
63
+ */
64
+ export async function verifyChain(tokens, rootKey, opts = {}) {
65
+ if (tokens.length === 0)
66
+ throw new Error("Tokens list cannot be empty");
67
+ enforceCaps(tokens); // H5 — before any crypto
68
+ // H4 — aud+nonce mandatory for any KB-bearing chain (fail closed).
69
+ if (tokens.length > 1 && (!opts.expectedAud || !opts.expectedNonce)) {
70
+ throw new Error("expectedAud and expectedNonce are required to verify a chain with KB-SD-JWT hops");
71
+ }
72
+ const skew = opts.clockSkewSeconds ?? DEFAULT_CLOCK_SKEW_SECONDS;
73
+ const now = opts.currentTime ?? Math.floor(Date.now() / 1000);
74
+ const payloads = [];
75
+ // Root (index 0).
76
+ const rootKeyResolved = typeof rootKey === "function" ? rootKey(tokens[0]) : rootKey;
77
+ let prev = await verifyRootSdJwt(tokens[0], rootKeyResolved);
78
+ checkTimeClaims([prev.verifiedPayload], 0, now, skew);
79
+ checkTimeClaims(prev.delegateItems, 0, now, skew);
80
+ payloads.push(...(prev.delegateItems.length ? prev.delegateItems : [prev.verifiedPayload]));
81
+ // KB-SD-JWT hops (index 1..n-1) verify under the previous hop's cnf.jwk.
82
+ for (let i = 1; i < tokens.length; i++) {
83
+ const isLast = i === tokens.length - 1;
84
+ const payload = await verifyKbHop(tokens[i], prev, {
85
+ expectedAud: isLast ? opts.expectedAud : undefined,
86
+ expectedNonce: isLast ? opts.expectedNonce : undefined,
87
+ requireTerminal: isLast, // H4 anti-truncation: last hop must be terminal-typ
88
+ });
89
+ const delegateItems = resolveDelegateItems(payload["delegate_payload"], tokens[i]);
90
+ if (!isLast && delegateItems.length > 1) {
91
+ throw new Error(`Token ${i}: delegate_payload has ${delegateItems.length} disclosed items, expected exactly 1`);
92
+ }
93
+ checkTimeClaims([payload], i, now, skew);
94
+ checkTimeClaims(delegateItems, i, now, skew);
95
+ payloads.push(...(delegateItems.length ? delegateItems : [payload]));
96
+ prev = { token: tokens[i], verifiedPayload: payload, delegateItems };
97
+ }
98
+ return payloads;
99
+ }
100
+ //# sourceMappingURL=chain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chain.js","sourceRoot":"","sources":["../../src/ap2/chain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAA4C,MAAM,aAAa,CAAC;AAC9G,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI7C;qDACqD;AACrD,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;IACtC,QAAQ,EAAE,CAAC;IACX,aAAa,EAAE,EAAE,GAAG,IAAI;IACxB,sBAAsB,EAAE,EAAE;CAC3B,CAAC,CAAC;AAEH,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAavC,SAAS,WAAW,CAAC,MAAqB;IACxC,IAAI,MAAM,CAAC,MAAM,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,iBAAiB,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC1F,CAAC;IACD,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACtB,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,UAAU,CAAC,aAAa,EAAE,CAAC;YAClD,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,YAAY,UAAU,CAAC,aAAa,QAAQ,CAAC,CAAC;QAC1E,CAAC;QACD,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,GAAG,UAAU,CAAC,sBAAsB,EAAE,CAAC;YAC7D,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,MAAM,sBAAsB,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;QACnH,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,kFAAkF;AAClF,SAAS,eAAe,CAAC,QAAgB,EAAE,UAAkB,EAAE,GAAW,EAAE,IAAY;IACtF,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACrB,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACtC,IAAI,OAAO,GAAG,KAAK,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,SAAS,UAAU,+BAA+B,CAAC,CAAC;YACjG,IAAI,GAAG,GAAG,GAAG,GAAG,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,SAAS,UAAU,eAAe,GAAG,EAAE,CAAC,CAAC;QACjF,CAAC;QACD,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACrB,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACtC,IAAI,OAAO,GAAG,KAAK,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,SAAS,UAAU,+BAA+B,CAAC,CAAC;YACjG,IAAI,GAAG,GAAG,GAAG,GAAG,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,SAAS,UAAU,0BAA0B,GAAG,EAAE,CAAC,CAAC;QAC5F,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAqB,EACrB,OAA0C,EAC1C,OAA2B,EAAE;IAE7B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACxE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,yBAAyB;IAE9C,mEAAmE;IACnE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,kFAAkF,CAAC,CAAC;IACtG,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,gBAAgB,IAAI,0BAA0B,CAAC;IACjE,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAW,EAAE,CAAC;IAE5B,kBAAkB;IAClB,MAAM,eAAe,GAAG,OAAO,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACrF,IAAI,IAAI,GAAkB,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;IAC5E,eAAe,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACtD,eAAe,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAClD,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAE5F,yEAAyE;IACzE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,CAAC,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QACvC,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;YACjD,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;YAClD,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS;YACtD,eAAe,EAAE,MAAM,EAAE,oDAAoD;SAC9E,CAAC,CAAC;QACH,MAAM,aAAa,GAAG,oBAAoB,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACnF,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,0BAA0B,aAAa,CAAC,MAAM,sCAAsC,CAAC,CAAC;QAClH,CAAC;QACD,eAAe,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QACzC,eAAe,CAAC,aAAa,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QAC7C,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACrE,IAAI,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;IACvE,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Typed mandate-chain wrappers + linkage + receipt reference.
3
+ *
4
+ * Ports AP2's `payment_mandate_chain.py` / `checkout_mandate_chain.py`: the
5
+ * per-hop effective payloads from `verifyChain` are parsed into a typed
6
+ * `[open, closed]` pair, then constraints are evaluated.
7
+ *
8
+ * Hardening H6 (stricter than AP2): AP2 takes the `transaction_id` /
9
+ * `checkout_hash` linkage as caller-supplied. We SELF-COMPUTE the
10
+ * `checkout_hash` from the embedded `checkout_jwt` and require it to match the
11
+ * mandate's own `checkout_hash` (mandatory) before evaluating constraints — a
12
+ * caller can't assert a linkage the bytes don't support.
13
+ */
14
+ import { type OpenPaymentMandate, type PaymentMandate, type OpenCheckoutMandate, type CheckoutMandate, type Checkout, type MandateContext } from "./types.js";
15
+ export interface PaymentChain {
16
+ open: OpenPaymentMandate;
17
+ closed: PaymentMandate;
18
+ }
19
+ /** Parse exactly two verified payloads into a typed payment chain. */
20
+ export declare function parsePaymentChain(payloads: unknown[]): PaymentChain;
21
+ export interface VerifyPaymentChainOptions {
22
+ /** The verified open-checkout-mandate hash to bind to (H6: pass the value
23
+ * self-computed by `verifyCheckoutChain`, not a caller assertion). */
24
+ expectedTransactionId?: string;
25
+ openCheckoutHash?: string;
26
+ mandateContext?: MandateContext;
27
+ }
28
+ /** Verify a payment chain's constraints + linkage. Returns [] when satisfied. */
29
+ export declare function verifyPaymentChain(chain: PaymentChain, opts?: VerifyPaymentChainOptions): string[];
30
+ export interface CheckoutChain {
31
+ open: OpenCheckoutMandate;
32
+ closed: CheckoutMandate;
33
+ }
34
+ export declare function parseCheckoutChain(payloads: unknown[]): CheckoutChain;
35
+ /** Decode the `Checkout` object from a `checkout_jwt` payload (port of
36
+ * `extract_parsed_checkout_object`). The merchant signature is out of AP2's
37
+ * scope (CHK-07); only the payload is decoded + schema-validated. */
38
+ export declare function extractCheckout(checkoutJwt: string): Checkout;
39
+ export interface VerifyCheckoutChainOptions {
40
+ /** `_sd_alg` of the closed mandate's SD-JWT (defaults to sha-256). */
41
+ sdAlg?: string;
42
+ expectedCheckoutHash?: string;
43
+ }
44
+ /**
45
+ * Verify a checkout chain: self-compute `checkout_hash` from the embedded
46
+ * `checkout_jwt` and require it to match the mandate (H6), then evaluate the
47
+ * open mandate's constraints against the decoded checkout. Returns [].
48
+ */
49
+ export declare function verifyCheckoutChain(chain: CheckoutChain, opts?: VerifyCheckoutChainOptions): string[];
50
+ /** The closed-mandate leaf JWT of a chain (port of `get_closed_mandate_jwt`):
51
+ * the bare issuer JWT (no disclosures) of the final `~~`-separated segment.
52
+ *
53
+ * NOTE: this is for embedding/identifying the closed mandate. It is NOT the
54
+ * receipt-reference input — use `receiptReference()` for that. (AP2's SDK
55
+ * `get_closed_mandate_jwt` docstring suggests `sha256` of this string as the
56
+ * reference, but the AP2 spec (AUTH-17) defines the reference as `sd_hash` over
57
+ * the final SD-JWT *including disclosures*; we follow the spec. The two differ.)
58
+ */
59
+ export declare function getClosedMandateJwt(presentationToken: string): string;
60
+ /**
61
+ * Stable receipt `reference` for a chain (AUTH-17 / SPEC-6): the base64url hash
62
+ * of the FINAL SD-JWT in the chain, computed "in the same manner as `sd_hash`"
63
+ * — i.e. `sd_hash` over the last segment's canonical SD-JWT form (issuer JWT +
64
+ * disclosures). This is the spec-authoritative reference; it is deliberately
65
+ * NOT `sha256(getClosedMandateJwt(chain))` (see that function's note).
66
+ */
67
+ export declare function receiptReference(presentationToken: string): string;
68
+ //# sourceMappingURL=chains.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chains.d.ts","sourceRoot":"","sources":["../../src/ap2/chains.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAML,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,QAAQ,EACb,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAOpB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,YAAY,CAQnE;AAED,MAAM,WAAW,yBAAyB;IACxC;0EACsE;IACtE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,GAAE,yBAA8B,GAAG,MAAM,EAAE,CAWtG;AAID,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,aAAa,CAQrE;AAED;;qEAEqE;AACrE,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,CAY7D;AAED,MAAM,WAAW,0BAA0B;IACzC,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,GAAE,0BAA+B,GAAG,MAAM,EAAE,CA0BzG;AAID;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAGrE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAIlE"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Typed mandate-chain wrappers + linkage + receipt reference.
3
+ *
4
+ * Ports AP2's `payment_mandate_chain.py` / `checkout_mandate_chain.py`: the
5
+ * per-hop effective payloads from `verifyChain` are parsed into a typed
6
+ * `[open, closed]` pair, then constraints are evaluated.
7
+ *
8
+ * Hardening H6 (stricter than AP2): AP2 takes the `transaction_id` /
9
+ * `checkout_hash` linkage as caller-supplied. We SELF-COMPUTE the
10
+ * `checkout_hash` from the embedded `checkout_jwt` and require it to match the
11
+ * mandate's own `checkout_hash` (mandatory) before evaluating constraints — a
12
+ * caller can't assert a linkage the bytes don't support.
13
+ */
14
+ import { Buffer } from "node:buffer";
15
+ import { computeValueHash, computeSdHash } from "./hash.js";
16
+ import { canonicalChainSegment, parseToken } from "./parse.js";
17
+ import { OpenPaymentMandateSchema, PaymentMandateSchema, OpenCheckoutMandateSchema, CheckoutMandateSchema, CheckoutSchema, } from "./types.js";
18
+ import { checkPaymentConstraints, checkCheckoutConstraints } from "./constraints.js";
19
+ const JWT_COMPACT_PARTS = 3;
20
+ /** Parse exactly two verified payloads into a typed payment chain. */
21
+ export function parsePaymentChain(payloads) {
22
+ if (payloads.length !== 2) {
23
+ throw new Error(`Payment mandate chain requires exactly 2 payloads, got ${payloads.length}`);
24
+ }
25
+ return {
26
+ open: OpenPaymentMandateSchema.parse(payloads[0]),
27
+ closed: PaymentMandateSchema.parse(payloads[1]),
28
+ };
29
+ }
30
+ /** Verify a payment chain's constraints + linkage. Returns [] when satisfied. */
31
+ export function verifyPaymentChain(chain, opts = {}) {
32
+ const violations = checkPaymentConstraints(chain.open, chain.closed, {
33
+ openCheckoutHash: opts.openCheckoutHash,
34
+ mandateContext: opts.mandateContext,
35
+ });
36
+ if (opts.expectedTransactionId !== undefined && opts.expectedTransactionId !== chain.closed.transaction_id) {
37
+ violations.push(`Payment transaction_id mismatch: expected ${opts.expectedTransactionId}, got ${chain.closed.transaction_id}`);
38
+ }
39
+ return violations;
40
+ }
41
+ export function parseCheckoutChain(payloads) {
42
+ if (payloads.length !== 2) {
43
+ throw new Error(`Checkout mandate chain requires exactly 2 payloads, got ${payloads.length}`);
44
+ }
45
+ return {
46
+ open: OpenCheckoutMandateSchema.parse(payloads[0]),
47
+ closed: CheckoutMandateSchema.parse(payloads[1]),
48
+ };
49
+ }
50
+ /** Decode the `Checkout` object from a `checkout_jwt` payload (port of
51
+ * `extract_parsed_checkout_object`). The merchant signature is out of AP2's
52
+ * scope (CHK-07); only the payload is decoded + schema-validated. */
53
+ export function extractCheckout(checkoutJwt) {
54
+ const parts = checkoutJwt.split(".");
55
+ if (parts.length !== JWT_COMPACT_PARTS) {
56
+ throw new Error("Malformed checkout_jwt: expected header.payload.signature");
57
+ }
58
+ let json;
59
+ try {
60
+ json = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
61
+ }
62
+ catch (e) {
63
+ throw new Error(`Invalid checkout_jwt payload: ${e.message}`);
64
+ }
65
+ return CheckoutSchema.parse(json);
66
+ }
67
+ /**
68
+ * Verify a checkout chain: self-compute `checkout_hash` from the embedded
69
+ * `checkout_jwt` and require it to match the mandate (H6), then evaluate the
70
+ * open mandate's constraints against the decoded checkout. Returns [].
71
+ */
72
+ export function verifyCheckoutChain(chain, opts = {}) {
73
+ const violations = [];
74
+ // H6 — self-computed linkage is mandatory.
75
+ const computed = computeValueHash(chain.closed.checkout_jwt, opts.sdAlg);
76
+ if (computed !== chain.closed.checkout_hash) {
77
+ violations.push(`Self-computed checkout_hash mismatch: hash(checkout_jwt)=${computed}, mandate checkout_hash=${chain.closed.checkout_hash}`);
78
+ return violations; // do not evaluate constraints against an unbound checkout
79
+ }
80
+ let checkout;
81
+ try {
82
+ checkout = extractCheckout(chain.closed.checkout_jwt);
83
+ }
84
+ catch (e) {
85
+ return [e.message];
86
+ }
87
+ violations.push(...checkCheckoutConstraints(chain.open, checkout));
88
+ if (opts.expectedCheckoutHash !== undefined && opts.expectedCheckoutHash !== chain.closed.checkout_hash) {
89
+ violations.push(`Checkout checkout_hash mismatch: expected ${opts.expectedCheckoutHash}, got ${chain.closed.checkout_hash}`);
90
+ }
91
+ return violations;
92
+ }
93
+ // ── Receipt reference ────────────────────────────────────────────────────────
94
+ /** The closed-mandate leaf JWT of a chain (port of `get_closed_mandate_jwt`):
95
+ * the bare issuer JWT (no disclosures) of the final `~~`-separated segment.
96
+ *
97
+ * NOTE: this is for embedding/identifying the closed mandate. It is NOT the
98
+ * receipt-reference input — use `receiptReference()` for that. (AP2's SDK
99
+ * `get_closed_mandate_jwt` docstring suggests `sha256` of this string as the
100
+ * reference, but the AP2 spec (AUTH-17) defines the reference as `sd_hash` over
101
+ * the final SD-JWT *including disclosures*; we follow the spec. The two differ.)
102
+ */
103
+ export function getClosedMandateJwt(presentationToken) {
104
+ const lastSegment = presentationToken.split("~~").at(-1);
105
+ return lastSegment.split("~", 1)[0];
106
+ }
107
+ /**
108
+ * Stable receipt `reference` for a chain (AUTH-17 / SPEC-6): the base64url hash
109
+ * of the FINAL SD-JWT in the chain, computed "in the same manner as `sd_hash`"
110
+ * — i.e. `sd_hash` over the last segment's canonical SD-JWT form (issuer JWT +
111
+ * disclosures). This is the spec-authoritative reference; it is deliberately
112
+ * NOT `sha256(getClosedMandateJwt(chain))` (see that function's note).
113
+ */
114
+ export function receiptReference(presentationToken) {
115
+ const segments = presentationToken.split("~~");
116
+ const last = parseToken(canonicalChainSegment(segments.at(-1), segments.length - 1, segments.length));
117
+ return computeSdHash(last);
118
+ }
119
+ //# sourceMappingURL=chains.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chains.js","sourceRoot":"","sources":["../../src/ap2/chains.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Payment-constraint evaluation — line-for-line port of AP2's
3
+ * `sdk/constraints.py` (commit e1ea56d). Each evaluator returns a list of
4
+ * violation messages ([] = satisfied). Violation strings match AP2's f-strings
5
+ * byte-for-byte where the inputs are reproducible (amounts, ids, names, dates);
6
+ * the few AP2 messages that embed a Python object `repr` (pisp / pre-set
7
+ * amount+instrument) are noted and use a readable JSON form instead — only the
8
+ * violation COUNT is asserted against AP2 for those.
9
+ *
10
+ * Closed-world rule (AUTH-15 / SPEC-39): an UNKNOWN constraint type MUST be
11
+ * treated as failing evaluation.
12
+ */
13
+ import { type Checkout, type Merchant, type MandateContext, type OpenCheckoutMandate, type OpenPaymentMandate, type PaymentMandate } from "./types.js";
14
+ interface EvalContext {
15
+ openCheckoutHash?: string;
16
+ mandateContext?: MandateContext;
17
+ /**
18
+ * Constraint types the caller requires to have been enforced on this mandate,
19
+ * e.g. `["payment.budget"]`.
20
+ *
21
+ * AP2 builds one evaluator per constraint PRESENT in the open mandate and never
22
+ * asserts which constraints ought to have been there, so a constraint withheld
23
+ * through selective disclosure produces no evaluator and no violation. An empty
24
+ * result therefore cannot distinguish "every constraint was evaluated and
25
+ * satisfied" from "nothing was evaluated". Declaring the set here makes the
26
+ * difference observable: a required constraint that is absent is reported
27
+ * rather than silently skipped.
28
+ *
29
+ * Opt-in and additive. Omit it and evaluation is byte-identical to AP2.
30
+ */
31
+ requiredConstraints?: string[];
32
+ }
33
+ /** Port of `merchant_matches`: id-first, else name+website (both non-empty). */
34
+ export declare function merchantMatches(candidate: Merchant, target: Merchant): boolean;
35
+ /** Port of `check_preset_payment_claims`: open mandate pre-set fields must be
36
+ * present unchanged in the closed mandate (SPEC-38 / step 2). */
37
+ export declare function checkPresetPaymentClaims(open: OpenPaymentMandate, closed: PaymentMandate): string[];
38
+ /**
39
+ * Verify a closed payment satisfies an open mandate's pre-set claims and
40
+ * constraints. Port of `check_payment_constraints`. Returns [] when satisfied.
41
+ */
42
+ export declare function checkPaymentConstraints(open: OpenPaymentMandate, closed: PaymentMandate, ctx?: EvalContext): string[];
43
+ /** Verify a checkout satisfies an open checkout mandate's constraints. Port of
44
+ * `check_checkout_constraints`. Returns [] when satisfied. */
45
+ export declare function checkCheckoutConstraints(open: OpenCheckoutMandate, checkout: Checkout): string[];
46
+ export {};
47
+ //# sourceMappingURL=constraints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constraints.d.ts","sourceRoot":"","sources":["../../src/ap2/constraints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAIL,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAKpB,UAAU,WAAW;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;;;;;;;;;;;OAaG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAQ9E;AAkID;iEACiE;AACjE,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,EAAE,CAenG;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,GAAE,WAAgB,GAAG,MAAM,EAAE,CA8BzH;AA+BD;8DAC8D;AAC9D,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,mBAAmB,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,CAMhG"}