@devlab.group/agent-commerce 1.4.1 → 1.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 (44) hide show
  1. package/README.md +168 -183
  2. package/dist/ap2.d.ts +21 -24
  3. package/dist/ap2.js +68 -82
  4. package/dist/ap2.js.map +1 -1
  5. package/dist/authorization-BpbWGGrf.d.ts +115 -0
  6. package/dist/{chunk-NWIQLJNU.js → chunk-74BSEXB5.js} +37 -34
  7. package/dist/chunk-74BSEXB5.js.map +1 -0
  8. package/dist/chunk-AFI2CEZS.js +10 -0
  9. package/dist/chunk-AFI2CEZS.js.map +1 -0
  10. package/dist/chunk-ATTWSFQP.js +680 -0
  11. package/dist/chunk-ATTWSFQP.js.map +1 -0
  12. package/dist/{chunk-ZEGFLRGV.js → chunk-IVJC5YLE.js} +63 -26
  13. package/dist/chunk-IVJC5YLE.js.map +1 -0
  14. package/dist/{chunk-GF6KHHKL.js → chunk-RAJGPGJG.js} +5 -8
  15. package/dist/chunk-RAJGPGJG.js.map +1 -0
  16. package/dist/chunk-V2V2E7HX.js +27 -0
  17. package/dist/chunk-V2V2E7HX.js.map +1 -0
  18. package/dist/cli/index.js +812 -573
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/constants-Bkq04FRZ.d.ts +48 -0
  21. package/dist/guardrails-71XwtXlT.d.ts +31 -0
  22. package/dist/index.d.ts +245 -265
  23. package/dist/index.js +1222 -626
  24. package/dist/index.js.map +1 -1
  25. package/dist/mcp.d.ts +6 -59
  26. package/dist/mcp.js +64 -53
  27. package/dist/mcp.js.map +1 -1
  28. package/dist/mpp.d.ts +50 -0
  29. package/dist/mpp.js +353 -0
  30. package/dist/mpp.js.map +1 -0
  31. package/dist/protocol-adapter-DTkrXTev.d.ts +238 -0
  32. package/dist/runtime-CiYbgGpi.d.ts +303 -0
  33. package/dist/{types-CJtwLu1c.d.ts → types-O6lg3Q5V.d.ts} +17 -23
  34. package/dist/x402.d.ts +43 -75
  35. package/dist/x402.js +13 -693
  36. package/dist/x402.js.map +1 -1
  37. package/package.json +13 -2
  38. package/dist/authorization-CPDcpJVa.d.ts +0 -123
  39. package/dist/chunk-GF6KHHKL.js.map +0 -1
  40. package/dist/chunk-NWIQLJNU.js.map +0 -1
  41. package/dist/chunk-ZEGFLRGV.js.map +0 -1
  42. package/dist/guardrails-Dmwi63KB.d.ts +0 -38
  43. package/dist/protocol-adapter-Bebmggf_.d.ts +0 -273
  44. package/dist/runtime-CKq3d_3K.d.ts +0 -340
package/dist/ap2.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { E as EnabledAp2Config } from './types-CJtwLu1c.js';
2
- export { A as AP2_CHECKOUT_MANDATE_VCT, a as AP2_CHECKOUT_PROFILE, b as AP2_DEFAULT_CLOCK_SKEW_SECONDS, c as AP2_MAX_CLOCK_SKEW_SECONDS, d as AP2_SIGNING_ALGORITHM, e as AP2_SPEC_VERSION, f as Ap2AuthorizationConfig, g as Ap2Mode, h as Ap2TrustedIssuer, i as Ap2TrustedKey } from './types-CJtwLu1c.js';
3
- import { A as AuthorizationProvider } from './authorization-CPDcpJVa.js';
4
- import { C as Clock, L as Logger } from './runtime-CKq3d_3K.js';
1
+ import { E as EnabledAp2Config } from './types-O6lg3Q5V.js';
2
+ export { A as AP2_CHECKOUT_MANDATE_VCT, a as AP2_CHECKOUT_PROFILE, b as AP2_DEFAULT_CLOCK_SKEW_SECONDS, c as AP2_MAX_CLOCK_SKEW_SECONDS, d as AP2_SIGNING_ALGORITHM, e as AP2_SPEC_VERSION, f as Ap2AuthorizationConfig, g as Ap2Mode, h as Ap2TrustedIssuer, i as Ap2TrustedKey } from './types-O6lg3Q5V.js';
3
+ import { A as AuthorizationProvider } from './authorization-BpbWGGrf.js';
4
+ import { C as Clock, L as Logger } from './runtime-CiYbgGpi.js';
5
5
 
6
6
  /**
7
7
  * A private ES256 key: either a private JWK (the pair of the public one in the
8
- * gateway's `checkoutIssuers`) or a PKCS#8 PEM, as `openssl` emits it.
8
+ * gateway's `checkoutIssuers`) or a PKCS#8 PEM, as `openssl` emits it
9
9
  */
10
10
  type Ap2SigningKey = Readonly<Record<string, unknown>> | string;
11
11
  interface CreateCheckoutJwtOptions {
@@ -19,12 +19,12 @@ interface CreateCheckoutJwtOptions {
19
19
  readonly resourceId: string;
20
20
  /**
21
21
  * The resource input this purchase is for, exactly as the buyer will send
22
- * it: no reserved fields, no request id, no transport metadata.
22
+ * it: no reserved fields, no request id, no transport metadata
23
23
  */
24
24
  readonly input: unknown;
25
25
  /**
26
26
  * Decimal string, never a number, and compared as a string: `0.10` and `0.1`
27
- * are different mandates. Take it from your own catalogue rather than from
27
+ * are different mandates. Take it from your own catalog rather than from
28
28
  * whatever the agent asked for.
29
29
  */
30
30
  readonly amount: string;
@@ -39,7 +39,7 @@ interface CreateCheckoutJwtOptions {
39
39
  readonly destination?: string;
40
40
  readonly network?: string;
41
41
  readonly asset?: string;
42
- /** Defaults to a random UUID. Recorded on the receipt and used for replay defence */
42
+ /** Defaults to a random UUID. Recorded on the receipt and used for replay defense */
43
43
  readonly jwtId?: string;
44
44
  /** Defaults to 900: a human approval sits inside this window */
45
45
  readonly expiresInSeconds?: number;
@@ -48,7 +48,7 @@ interface CreateCheckoutJwtOptions {
48
48
  }
49
49
  /**
50
50
  * Returns the compact JWT to hand to the agent, which wraps it in the Checkout
51
- * Mandate the buyer signs.
51
+ * Mandate the buyer signs
52
52
  */
53
53
  declare function createCheckoutJwt(options: CreateCheckoutJwtOptions): Promise<string>;
54
54
 
@@ -62,9 +62,9 @@ declare function createCheckoutJwt(options: CreateCheckoutJwtOptions): Promise<s
62
62
  /** What this provider actually verifies */
63
63
  declare const AP2_CAPABILITIES: readonly string[];
64
64
  /**
65
- * Everything an AP2 client may reasonably expect and will not get here.
66
- * Complete on purpose: a short list reads as "mostly compatible", which is the
67
- * blanket claim alpha honesty forbids.
65
+ * Major AP2 features this provider does not support, published in its
66
+ * descriptor and printed by `doctor`, so no client assumes full protocol
67
+ * support
68
68
  */
69
69
  declare const AP2_UNSUPPORTED: readonly string[];
70
70
 
@@ -74,7 +74,7 @@ declare const AP2_UNSUPPORTED: readonly string[];
74
74
  * `CommerceError.message` reaches the client, and a mandate carries the
75
75
  * buyer's purchase and often personal data, so messages are fixed phrases from
76
76
  * the list below. The machine-readable code goes in `details.reason` and the
77
- * original exception on `cause`, which is never serialised.
77
+ * original exception on `cause`, which is never serialized.
78
78
  *
79
79
  * Three codes: AUTHORIZATION_INVALID when the buyer's mandate is bad,
80
80
  * AUTHORIZATION_REPLAYED when it is good but spent, and
@@ -84,9 +84,11 @@ declare const AP2_UNSUPPORTED: readonly string[];
84
84
 
85
85
  /**
86
86
  * Coarse on purpose: a client learns roughly where its mandate was refused,
87
- * not which check failed. Finer detail is an oracle for probing trust policy.
87
+ * not which check failed. Finer detail is an oracle for probing trust policy,
88
+ * so a mandate signed with a disallowed algorithm reports `invalid_signature`
89
+ * and one declaring a digest other than sha-256 `malformed_presentation`.
88
90
  */
89
- declare const AP2_REJECTION_REASONS: readonly ["malformed_presentation", "untrusted_issuer", "unknown_key", "invalid_signature", "unsupported_algorithm", "invalid_claims", "expired", "wrong_audience", "unsupported_mandate_type", "checkout_binding_failed", "purchase_mismatch"];
91
+ declare const AP2_REJECTION_REASONS: readonly ["malformed_presentation", "untrusted_issuer", "unknown_key", "invalid_signature", "invalid_claims", "expired", "wrong_audience", "unsupported_mandate_type", "checkout_binding_failed", "purchase_mismatch"];
90
92
  type Ap2RejectionReason = (typeof AP2_REJECTION_REASONS)[number];
91
93
 
92
94
  /**
@@ -96,7 +98,7 @@ type Ap2RejectionReason = (typeof AP2_REJECTION_REASONS)[number];
96
98
  */
97
99
  type Ap2AuthorizationState = 'reserved' | 'consumed' | 'released' | 'uncertain';
98
100
  /**
99
- * A digest and a few identifiers: everything replay defence needs, and nothing
101
+ * A digest and a few identifiers: everything replay defense needs, and nothing
100
102
  * a leaked database would hand an attacker
101
103
  */
102
104
  interface Ap2ReservationRequest {
@@ -139,8 +141,8 @@ interface Ap2ReplayStore {
139
141
  * The AP2 authorization provider: the seam between core's generic contract and
140
142
  * the mandate machinery.
141
143
  *
142
- * Owns the replay database, so the gateway owns this object's lifetime and
143
- * closes it on shutdown.
144
+ * It owns its replay store, so whoever creates it must call `close()`:
145
+ * `createGateway` does not close authorization providers.
144
146
  */
145
147
 
146
148
  interface Ap2AuthorizationProviderOptions {
@@ -151,14 +153,9 @@ interface Ap2AuthorizationProviderOptions {
151
153
  readonly replayStore?: Ap2ReplayStore;
152
154
  }
153
155
  interface Ap2AuthorizationProvider extends AuthorizationProvider {
154
- /** Closes the replay database */
156
+ /** Closes the replay store */
155
157
  close(): void;
156
158
  }
157
- /**
158
- * A reservation is keyed by the mandate reference, so the handle and the
159
- * receipt's identity are the same digest. Kept as one name rather than two
160
- * fields that must never disagree.
161
- */
162
159
  declare function createAp2AuthorizationProvider(options: Ap2AuthorizationProviderOptions): Ap2AuthorizationProvider;
163
160
 
164
161
  export { AP2_CAPABILITIES, AP2_REJECTION_REASONS, AP2_UNSUPPORTED, type Ap2AuthorizationProvider, type Ap2AuthorizationProviderOptions, type Ap2RejectionReason, type Ap2SigningKey, type CreateCheckoutJwtOptions, EnabledAp2Config, createAp2AuthorizationProvider as ap2, createAp2AuthorizationProvider, createCheckoutJwt };
package/dist/ap2.js CHANGED
@@ -1,8 +1,9 @@
1
- import { AP2_CHECKOUT_MANDATE_VCT, AP2_SIGNING_ALGORITHM, AP2_DIGEST_ALGORITHM, AP2_CHECKOUT_PROFILE, AP2_SPEC_VERSION, AP2_KEY_TYPE, AP2_JWK_CURVE, openSqliteDatabase } from './chunk-GF6KHHKL.js';
2
- export { AP2_CHECKOUT_MANDATE_VCT, AP2_CHECKOUT_PROFILE, AP2_DEFAULT_CLOCK_SKEW_SECONDS, AP2_MAX_CLOCK_SKEW_SECONDS, AP2_SIGNING_ALGORITHM, AP2_SPEC_VERSION } from './chunk-GF6KHHKL.js';
3
- import { systemClock, NOOP_LOGGER, CommerceError, PACKAGE_VERSION } from './chunk-ZEGFLRGV.js';
4
- import { SignJWT, importPKCS8, importJWK, jwtVerify } from 'jose';
1
+ import { AP2_CHECKOUT_MANDATE_VCT, AP2_SIGNING_ALGORITHM, AP2_DIGEST_ALGORITHM, AP2_CHECKOUT_PROFILE, AP2_SPEC_VERSION, AP2_KEY_TYPE, AP2_JWK_CURVE, openSqliteDatabase } from './chunk-RAJGPGJG.js';
2
+ export { AP2_CHECKOUT_MANDATE_VCT, AP2_CHECKOUT_PROFILE, AP2_DEFAULT_CLOCK_SKEW_SECONDS, AP2_MAX_CLOCK_SKEW_SECONDS, AP2_SIGNING_ALGORITHM, AP2_SPEC_VERSION } from './chunk-RAJGPGJG.js';
3
+ import { systemClock, NOOP_LOGGER, CommerceError, isRecord, PACKAGE_VERSION } from './chunk-IVJC5YLE.js';
4
+ import { SignJWT, importPKCS8, importJWK, jwtVerify, decodeProtectedHeader, decodeJwt } from 'jose';
5
5
  import canonicalize from 'canonicalize';
6
+ import { createHash } from 'crypto';
6
7
  import { decodeSdJwt, splitSdJwt, getClaims } from '@sd-jwt/core';
7
8
 
8
9
  // src/authorization/ap2/errors.ts
@@ -11,7 +12,6 @@ var AP2_REJECTION_REASONS = [
11
12
  "untrusted_issuer",
12
13
  "unknown_key",
13
14
  "invalid_signature",
14
- "unsupported_algorithm",
15
15
  "invalid_claims",
16
16
  "expired",
17
17
  "wrong_audience",
@@ -24,7 +24,6 @@ var MESSAGES = {
24
24
  untrusted_issuer: "The mandate was issued by a party this merchant does not trust.",
25
25
  unknown_key: "The mandate names a signing key this merchant does not trust.",
26
26
  invalid_signature: "The mandate signature did not verify.",
27
- unsupported_algorithm: "The mandate uses a signature or digest algorithm that is not accepted.",
28
27
  invalid_claims: "The mandate is missing required claims or they are malformed.",
29
28
  expired: "The mandate is expired or not yet valid.",
30
29
  wrong_audience: "The mandate is addressed to a different audience.",
@@ -63,6 +62,9 @@ function ap2Unavailable(detail, context = {}) {
63
62
  }
64
63
  );
65
64
  }
65
+ function sha256(data) {
66
+ return createHash("sha256").update(data).digest();
67
+ }
66
68
 
67
69
  // src/authorization/ap2/profile.ts
68
70
  var REQUIRED_PROFILE_CLAIMS = [
@@ -73,20 +75,25 @@ var REQUIRED_PROFILE_CLAIMS = [
73
75
  "currency",
74
76
  "payment_method"
75
77
  ];
76
- async function computeInputHash(input) {
78
+ function computeInputHash(input) {
77
79
  const canonical = canonicalize(input ?? {});
78
80
  if (canonical === void 0) {
79
- throw new TypeError("resource input is not canonicalizable JSON");
81
+ throw new CommerceError("INTERNAL_ERROR", "resource input is not canonicalizable JSON");
80
82
  }
81
- const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonical));
82
- return Buffer.from(digest).toString("base64url");
83
+ return sha256(canonical).toString("base64url");
83
84
  }
84
- function asRecord(value) {
85
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
85
+ var EVM_ADDRESS = /^0x[0-9a-f]{40}$/i;
86
+ function sameCoordinate(declared, expected) {
87
+ if (typeof declared === "string" && expected !== void 0) {
88
+ if (EVM_ADDRESS.test(declared) && EVM_ADDRESS.test(expected)) {
89
+ return declared.toLowerCase() === expected.toLowerCase();
90
+ }
91
+ }
92
+ return declared === expected;
86
93
  }
87
94
  async function bindMandateToPurchase(mandate, context, errorContext) {
88
- const profile = asRecord(mandate.checkoutClaims["agent_commerce"]);
89
- if (profile === void 0) throw ap2Rejected("purchase_mismatch", errorContext);
95
+ const profile = mandate.checkoutClaims["agent_commerce"];
96
+ if (!isRecord(profile)) throw ap2Rejected("purchase_mismatch", errorContext);
90
97
  for (const claim of REQUIRED_PROFILE_CLAIMS) {
91
98
  const value = profile[claim];
92
99
  if (typeof value !== "string" || value.length === 0) {
@@ -94,7 +101,7 @@ async function bindMandateToPurchase(mandate, context, errorContext) {
94
101
  }
95
102
  }
96
103
  const requirement = context.requirement;
97
- const expectedInputHash = await computeInputHash(context.input);
104
+ const expectedInputHash = computeInputHash(context.input);
98
105
  const mustMatch = [
99
106
  ["profile", AP2_CHECKOUT_PROFILE],
100
107
  ["resource_id", context.resourceId],
@@ -115,14 +122,8 @@ async function bindMandateToPurchase(mandate, context, errorContext) {
115
122
  ]) {
116
123
  const declared = profile[claim];
117
124
  if (declared === void 0 && expected === void 0) continue;
118
- if (declared !== expected) throw ap2Rejected("purchase_mismatch", errorContext);
125
+ if (!sameCoordinate(declared, expected)) throw ap2Rejected("purchase_mismatch", errorContext);
119
126
  }
120
- return {
121
- resourceId: profile["resource_id"],
122
- amount: profile["amount"],
123
- currency: profile["currency"],
124
- paymentMethod: profile["payment_method"]
125
- };
126
127
  }
127
128
 
128
129
  // src/authorization/ap2/checkout-signer.ts
@@ -184,7 +185,7 @@ async function createCheckoutJwt(options) {
184
185
  const agentCommerce = {
185
186
  profile: AP2_CHECKOUT_PROFILE,
186
187
  resource_id: resourceId,
187
- input_hash: await computeInputHash(options.input),
188
+ input_hash: computeInputHash(options.input),
188
189
  amount,
189
190
  currency,
190
191
  payment_method: paymentMethod,
@@ -209,8 +210,7 @@ var AP2_CAPABILITIES = [
209
210
  "replay-defence"
210
211
  ];
211
212
  var AP2_UNSUPPORTED = [
212
- // Mandate kinds. Open mandates carry spending constraints nothing here
213
- // evaluates, so accepting one would tell a buyer their limits were checked.
213
+ // Mandate kinds. Open mandates are refused: see AP2_CHECKOUT_MANDATE_VCT
214
214
  "autonomous mode",
215
215
  "open checkout mandates (mandate.checkout.open.1)",
216
216
  "intent mandates",
@@ -226,7 +226,6 @@ var AP2_UNSUPPORTED = [
226
226
  "digest algorithms other than sha-256",
227
227
  // Roles this gateway does not play
228
228
  "mandate issuance",
229
- "merchant checkout JWT issuance",
230
229
  "AP2 over the ACP checkout adapter"
231
230
  ];
232
231
  function buildAp2Descriptor(implementationVersion) {
@@ -277,17 +276,17 @@ function createAp2ReplayStore(options) {
277
276
  const reserveTx = db.transaction((request) => {
278
277
  const at = new Date(now()).toISOString();
279
278
  const existing = selectByReference.get(request.reference);
280
- if (existing !== void 0) {
281
- if (existing.state !== "released") {
282
- return { kind: "replayed", state: existing.state };
283
- }
284
- reReserve.run({ reference: request.reference, request_id: request.requestId, at });
285
- return { kind: "reserved" };
279
+ if (existing !== void 0 && existing.state !== "released") {
280
+ return { kind: "replayed", state: existing.state };
286
281
  }
287
282
  const sameCheckout = selectLiveByJti.get(request.checkoutJti, request.reference);
288
283
  if (sameCheckout !== void 0) {
289
284
  return { kind: "replayed", state: sameCheckout.state };
290
285
  }
286
+ if (existing !== void 0) {
287
+ reReserve.run({ reference: request.reference, request_id: request.requestId, at });
288
+ return { kind: "reserved" };
289
+ }
291
290
  insert.run({
292
291
  reference: request.reference,
293
292
  checkout_jti: request.checkoutJti,
@@ -350,16 +349,6 @@ function migrate(db) {
350
349
  });
351
350
  apply();
352
351
  }
353
- function decodeSegment(jwt, index) {
354
- const segment = jwt.split(".")[index];
355
- if (segment === void 0) return void 0;
356
- try {
357
- const parsed = JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
358
- return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : void 0;
359
- } catch {
360
- return void 0;
361
- }
362
- }
363
352
  async function verifyCheckoutJwt(mandateClaims, deps, context) {
364
353
  const compact = mandateClaims["checkout_jwt"];
365
354
  const expectedHash = mandateClaims["checkout_hash"];
@@ -369,17 +358,16 @@ async function verifyCheckoutJwt(mandateClaims, deps, context) {
369
358
  if (typeof expectedHash !== "string" || expectedHash.length === 0) {
370
359
  throw ap2Rejected("invalid_claims", context);
371
360
  }
372
- await requireMatchingHash(compact, expectedHash, context);
373
- const header = decodeSegment(compact, 0);
374
- const unverifiedPayload = decodeSegment(compact, 1);
375
- if (header === void 0 || unverifiedPayload === void 0) {
376
- throw ap2Rejected("checkout_binding_failed", context);
361
+ requireMatchingHash(compact, expectedHash, context);
362
+ let header;
363
+ let unverifiedPayload;
364
+ try {
365
+ header = decodeProtectedHeader(compact);
366
+ unverifiedPayload = decodeJwt(compact);
367
+ } catch (cause) {
368
+ throw ap2Rejected("checkout_binding_failed", { ...context, cause });
377
369
  }
378
- const { issuer, key } = await deps.trust.resolve(
379
- unverifiedPayload["iss"],
380
- header["kid"],
381
- context
382
- );
370
+ const { issuer, key } = await deps.trust.resolve(unverifiedPayload.iss, header.kid, context);
383
371
  const options = {
384
372
  algorithms: [AP2_SIGNING_ALGORITHM],
385
373
  issuer: issuer.issuer,
@@ -407,20 +395,15 @@ async function verifyCheckoutJwt(mandateClaims, deps, context) {
407
395
  if (iat > nowSeconds + deps.clockSkewSeconds) throw ap2Rejected("expired", context);
408
396
  return { issuer: issuer.issuer, jwtId: jti, claims };
409
397
  }
410
- async function requireMatchingHash(compact, expected, context) {
411
- const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(compact));
412
- const actual = Buffer.from(digest).toString("base64url");
398
+ function requireMatchingHash(compact, expected, context) {
399
+ const actual = sha256(compact).toString("base64url");
413
400
  if (actual !== expected) throw ap2Rejected("checkout_binding_failed", context);
414
401
  }
415
- async function hasher(data, algorithm) {
402
+ function hasher(data, algorithm) {
416
403
  if (algorithm.toLowerCase() !== AP2_DIGEST_ALGORITHM) {
417
- throw new Error(`unsupported digest algorithm ${algorithm}`);
404
+ throw ap2Rejected("malformed_presentation");
418
405
  }
419
- const bytes = typeof data === "string" ? new TextEncoder().encode(data) : new Uint8Array(data);
420
- return new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
421
- }
422
- function asRecord2(value) {
423
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
406
+ return sha256(typeof data === "string" ? data : new Uint8Array(data));
424
407
  }
425
408
  async function verifyMandate(presentation, deps, context) {
426
409
  let decoded;
@@ -434,26 +417,25 @@ async function verifyMandate(presentation, deps, context) {
434
417
  if (decoded.kbJwt !== void 0) {
435
418
  throw ap2Rejected("unsupported_mandate_type", context);
436
419
  }
437
- const rawPayload = asRecord2(decoded.jwt.payload);
438
- const header = asRecord2(decoded.jwt.header);
439
- if (rawPayload === void 0 || header === void 0) {
420
+ const rawPayload = decoded.jwt.payload;
421
+ const header = decoded.jwt.header;
422
+ if (!isRecord(rawPayload) || !isRecord(header)) {
440
423
  throw ap2Rejected("malformed_presentation", context);
441
424
  }
442
425
  const { issuer, key } = await deps.trust.resolve(rawPayload["iss"], header["kid"], context);
443
426
  const options = {
444
- // Redundant today (the key is EC, so jose already refuses `alg: none` and
445
- // a forged HMAC) and kept for the day this resolves a key set instead of
446
- // one key, where the header would get to pick
427
+ // Redundant with one resolved EC key, which already refuses `alg: none` and
428
+ // a forged HMAC. It stops the header picking the algorithm if a key set is
429
+ // ever resolved instead.
447
430
  algorithms: [AP2_SIGNING_ALGORITHM],
448
- // Also redundant, and for the same reason: the key was resolved *from*
449
- // `iss` against the configured allowlist, so nothing reaching here can
450
- // carry a different one. It backstops a future resolver that matches on
451
- // something else. `audience` below is not redundant - `aud` is the
452
- // presenter's claim, checked against what the operator configured.
431
+ // Also redundant, since the key was resolved *from* `iss`, and kept as a
432
+ // backstop for a resolver that matches on something else. `audience` is
433
+ // not redundant: `aud` is the presenter's claim, checked against the
434
+ // configured value.
453
435
  issuer: issuer.issuer,
454
436
  audience: issuer.audience,
455
437
  clockTolerance: deps.clockSkewSeconds,
456
- // Injected clock, not jose's `Date.now()`, so expiry tests are tests
438
+ // Injected clock, not jose's `Date.now()`, so expiry is testable
457
439
  currentDate: deps.clock.now()
458
440
  };
459
441
  let verifiedPayload;
@@ -536,9 +518,8 @@ function createTrustStore(issuers) {
536
518
  }
537
519
 
538
520
  // src/authorization/ap2/verifier.ts
539
- async function mandateReference(signedToken) {
540
- const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(signedToken));
541
- return `sha256:${Buffer.from(digest).toString("base64url")}`;
521
+ function mandateReference(signedToken) {
522
+ return `sha256:${sha256(signedToken).toString("base64url")}`;
542
523
  }
543
524
  function createAp2MandateVerifier(options) {
544
525
  const mandateTrust = createTrustStore(options.config.trust.mandateIssuers);
@@ -556,12 +537,11 @@ function createAp2MandateVerifier(options) {
556
537
  context
557
538
  );
558
539
  return {
559
- reference: await mandateReference(mandate.signedToken),
540
+ reference: mandateReference(mandate.signedToken),
560
541
  mandateIssuer: mandate.issuer,
561
542
  checkoutIssuer: checkout.issuer,
562
543
  checkoutJwtId: checkout.jwtId,
563
- checkoutClaims: checkout.claims,
564
- mandateClaims: mandate.claims
544
+ checkoutClaims: checkout.claims
565
545
  };
566
546
  },
567
547
  trustedIssuers() {
@@ -608,6 +588,8 @@ function createAp2AuthorizationProvider(options) {
608
588
  status: "verified",
609
589
  method: "ap2",
610
590
  reference: mandate.reference,
591
+ // The reservation handle is the reference itself, so the two can never
592
+ // name different mandates
611
593
  reservationId: mandate.reference,
612
594
  // Opaque identifiers an operator can reconcile with. Never a claim from
613
595
  // the mandate: those carry the buyer's purchase and their personal data.
@@ -643,14 +625,18 @@ function createAp2AuthorizationProvider(options) {
643
625
  },
644
626
  async health() {
645
627
  const startedAt = clock.monotonicMs();
646
- const trusted = verifier.trustedIssuers();
647
628
  const checkedAt = clock.nowIso();
648
- const durationMs = Math.round(clock.monotonicMs() - startedAt);
629
+ let available = true;
649
630
  try {
650
631
  replay.stateOf("health-probe");
651
632
  } catch {
633
+ available = false;
634
+ }
635
+ const durationMs = Math.round(clock.monotonicMs() - startedAt);
636
+ if (!available) {
652
637
  return { status: "fail", checkedAt, durationMs, detail: "replay-store-unavailable" };
653
638
  }
639
+ const trusted = verifier.trustedIssuers();
654
640
  return {
655
641
  status: "pass",
656
642
  checkedAt,