@devlab.group/agent-commerce 1.5.0 → 1.6.1

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 (45) hide show
  1. package/README.md +73 -71
  2. package/dist/ap2.d.ts +20 -23
  3. package/dist/ap2.js +53 -75
  4. package/dist/ap2.js.map +1 -1
  5. package/dist/authorization-BpbWGGrf.d.ts +115 -0
  6. package/dist/chunk-AFI2CEZS.js +10 -0
  7. package/dist/chunk-AFI2CEZS.js.map +1 -0
  8. package/dist/{chunk-PBYN7EK2.js → chunk-CBJIT7QP.js} +10 -9
  9. package/dist/chunk-CBJIT7QP.js.map +1 -0
  10. package/dist/{chunk-NKNXVDHN.js → chunk-JKEKKXRJ.js} +45 -66
  11. package/dist/chunk-JKEKKXRJ.js.map +1 -0
  12. package/dist/{chunk-5GEWKNIH.js → chunk-N5KRK7YA.js} +63 -27
  13. package/dist/chunk-N5KRK7YA.js.map +1 -0
  14. package/dist/{chunk-6D2EZ5ME.js → chunk-V2V2E7HX.js} +2 -2
  15. package/dist/{chunk-6D2EZ5ME.js.map → chunk-V2V2E7HX.js.map} +1 -1
  16. package/dist/{chunk-US622BKF.js → chunk-VKDXMXHL.js} +5 -8
  17. package/dist/chunk-VKDXMXHL.js.map +1 -0
  18. package/dist/cli/index.js +501 -494
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/{constants-6mypC60h.d.ts → constants-Bkq04FRZ.d.ts} +1 -3
  21. package/dist/guardrails-71XwtXlT.d.ts +31 -0
  22. package/dist/index.d.ts +230 -265
  23. package/dist/index.js +1050 -607
  24. package/dist/index.js.map +1 -1
  25. package/dist/mcp.d.ts +6 -59
  26. package/dist/mcp.js +61 -53
  27. package/dist/mcp.js.map +1 -1
  28. package/dist/mpp.d.ts +4 -4
  29. package/dist/mpp.js +21 -19
  30. package/dist/mpp.js.map +1 -1
  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 +10 -9
  36. package/dist/x402.js.map +1 -1
  37. package/package.json +7 -5
  38. package/dist/authorization-DA7mjy_O.d.ts +0 -123
  39. package/dist/chunk-5GEWKNIH.js.map +0 -1
  40. package/dist/chunk-NKNXVDHN.js.map +0 -1
  41. package/dist/chunk-PBYN7EK2.js.map +0 -1
  42. package/dist/chunk-US622BKF.js.map +0 -1
  43. package/dist/guardrails-Dmwi63KB.d.ts +0 -38
  44. package/dist/protocol-adapter-APRjs1E2.d.ts +0 -273
  45. package/dist/runtime-DC5XQ9Cc.d.ts +0 -351
package/dist/index.js CHANGED
@@ -1,16 +1,18 @@
1
- import { AP2_SPEC_VERSION, AP2_MODES, AP2_DEFAULT_CLOCK_SKEW_SECONDS, AP2_JWK_MEMBERS, AP2_KEY_TYPE, AP2_SIGNING_ALGORITHM, AP2_JWK_CURVE, AP2_JWK_COORDINATE_BYTES, openSqliteDatabase, AP2_MAX_CLOCK_SKEW_SECONDS } from './chunk-US622BKF.js';
2
- import { MPP_DEFAULT_NETWORK, isMppNetwork, MPP_NETWORKS, MPP_MIN_CHALLENGE_SECRET_LENGTH, MPP_PROFILE } from './chunk-6D2EZ5ME.js';
3
- import { resolveX402Deployment, resolveDeploymentMode, requireNetworkProfile } from './chunk-PBYN7EK2.js';
4
- import { RESERVED_INPUT_FIELDS, PROTOCOL_NAMES, PAYMENT_METHOD_NAMES, PAYMENT_HEADER, AUTHORIZATION_HEADER, CommerceError, systemClock, NOOP_LOGGER, PACKAGE_VERSION, toCommerceError, DEFAULT_BACKEND_TIMEOUT_MS, toErrorEnvelope, isHttpProtocolAdapter, isCommerceError, parseAuthorizationHeader, toPaymentRequiredEnvelope, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER } from './chunk-5GEWKNIH.js';
5
- export { AUTHORIZATION_HEADER, AUTHORIZATION_INPUT_FIELD, COMMERCE_ERROR_CODES, COMMERCE_ERROR_HTTP_STATUS, COMMERCE_EVENT_TYPES, CommerceError, DEFAULT_BACKEND_TIMEOUT_MS, DELIVERY_SUMMARY_META_KEY, MAX_AUTHORIZATION_HEADER_BYTES, NOOP_LOGGER, PAYMENT_HEADER, PAYMENT_INPUT_FIELD, PAYMENT_METHOD_NAMES, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, PROTOCOL_NAMES, RESERVED_INPUT_FIELDS, RETRYABLE_ERROR_CODES, extractReservedInputFields, isCommerceError, isHttpProtocolAdapter, isPaymentRequiredEnvelope, parseAuthorizationHeader, parseAuthorizationSubmission, systemClock, toCommerceError, toDeliverySummary, toErrorEnvelope, toPaymentRequiredEnvelope } from './chunk-5GEWKNIH.js';
1
+ import { AP2_SPEC_VERSION, AP2_MODES, AP2_DEFAULT_CLOCK_SKEW_SECONDS, AP2_JWK_MEMBERS, AP2_KEY_TYPE, AP2_SIGNING_ALGORITHM, AP2_JWK_CURVE, AP2_JWK_COORDINATE_BYTES, openSqliteDatabase, AP2_MAX_CLOCK_SKEW_SECONDS } from './chunk-VKDXMXHL.js';
2
+ import { MCP_TOOL_NAME_PATTERN } from './chunk-AFI2CEZS.js';
3
+ import { MPP_DEFAULT_NETWORK, isMppNetwork, MPP_NETWORKS, MPP_MIN_CHALLENGE_SECRET_LENGTH, MPP_PROFILE } from './chunk-V2V2E7HX.js';
4
+ import { resolveX402Deployment, resolveDeploymentMode, requireNetworkProfile } from './chunk-CBJIT7QP.js';
5
+ import { RESERVED_INPUT_FIELDS, PROTOCOL_NAMES, PAYMENT_METHOD_NAMES, PAYMENT_HEADER, AUTHORIZATION_HEADER, isRecord, CommerceError, systemClock, NOOP_LOGGER, PACKAGE_VERSION, toCommerceError, DEFAULT_BACKEND_TIMEOUT_MS, isCommerceError, describeError, isHttpProtocolAdapter, toLogInfo, extractReservedInputFields, parseAuthorizationHeader, toPaymentRequiredEnvelope, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, toErrorEnvelope, DELIVERY_SUMMARY_META_KEY, toDeliverySummary } from './chunk-N5KRK7YA.js';
6
+ export { AUTHORIZATION_HEADER, AUTHORIZATION_INPUT_FIELD, COMMERCE_ERROR_CODES, COMMERCE_ERROR_HTTP_STATUS, COMMERCE_EVENT_TYPES, CommerceError, DEFAULT_BACKEND_TIMEOUT_MS, DELIVERY_SUMMARY_META_KEY, MAX_AUTHORIZATION_HEADER_BYTES, NOOP_LOGGER, PAYMENT_HEADER, PAYMENT_INPUT_FIELD, PAYMENT_METHOD_NAMES, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, PROTOCOL_NAMES, RESERVED_INPUT_FIELDS, RETRYABLE_ERROR_CODES, extractReservedInputFields, isCommerceError, isHttpProtocolAdapter, isPaymentRequiredEnvelope, parseAuthorizationHeader, parseAuthorizationSubmission, systemClock, toCommerceError, toDeliverySummary, toErrorEnvelope, toPaymentRequiredEnvelope } from './chunk-N5KRK7YA.js';
6
7
  import { promises, accessSync, constants } from 'fs';
7
8
  import * as path from 'path';
8
9
  import { parse, YAMLError } from 'yaml';
9
10
  import { z } from 'zod';
11
+ import { isDeepStrictEqual } from 'util';
12
+ import Fastify from 'fastify';
10
13
  import { randomUUID, timingSafeEqual, createHash } from 'crypto';
11
14
  import { createRequire } from 'module';
12
15
  import pino from 'pino';
13
- import Fastify from 'fastify';
14
16
  import Ajv2020 from 'ajv/dist/2020.js';
15
17
  import addFormats from 'ajv-formats';
16
18
 
@@ -26,7 +28,7 @@ function substituteEnv(value, env, path2 = "$") {
26
28
  if (Array.isArray(value)) {
27
29
  return value.map((entry, index) => substituteEnv(entry, env, `${path2}[${index}]`));
28
30
  }
29
- if (isPlainObject(value)) {
31
+ if (isRecord(value)) {
30
32
  const result = {};
31
33
  for (const [key, entry] of Object.entries(value)) {
32
34
  result[key] = substituteEnv(entry, env, `${path2}.${key}`);
@@ -43,16 +45,15 @@ function assertTemplateIsSupported(value, path2) {
43
45
  if (NESTED_PLACEHOLDER.test(value)) {
44
46
  throw new CommerceError(
45
47
  "CONFIG_INVALID",
46
- `Configuration value at "${path2}" nests placeholders (e.g. "\${A:-\${B}}"), which is not supported \u2014 the inner one is not resolved and its closing brace ends up in the value. Use a plain default, or set the variable.`,
48
+ `Configuration value at "${path2}" nests placeholders (e.g. "\${A:-\${B}}"), which is not supported: the inner one is not resolved and its closing brace ends up in the value. Use a plain default, or set the variable.`,
47
49
  { details: { path: path2 } }
48
50
  );
49
51
  }
50
- BRACE_TOKEN.lastIndex = 0;
51
52
  for (const [token] of value.matchAll(BRACE_TOKEN)) {
52
53
  if (SUPPORTED_TOKEN.test(token)) continue;
53
54
  throw new CommerceError(
54
55
  "CONFIG_INVALID",
55
- `Configuration value at "${path2}" contains "${token}", which is not a supported placeholder. Only "\${VAR}" and "\${VAR:-default}" are recognised; shell forms such as "\${VAR-default}", "\${VAR:=default}" and "\${VAR:?message}" would load as literal text.`,
56
+ `Configuration value at "${path2}" contains "${token}", which is not a supported placeholder. Only "\${VAR}" and "\${VAR:-default}" are recognized; shell forms such as "\${VAR-default}", "\${VAR:=default}" and "\${VAR:?message}" would load as literal text.`,
56
57
  { details: { path: path2, token } }
57
58
  );
58
59
  }
@@ -77,9 +78,6 @@ function substituteOnce(value, env, path2) {
77
78
  }
78
79
  );
79
80
  }
80
- function isPlainObject(value) {
81
- return typeof value === "object" && value !== null && !Array.isArray(value);
82
- }
83
81
 
84
82
  // src/config/filename.ts
85
83
  var DEFAULT_CONFIG_FILENAME = "config.yaml";
@@ -98,7 +96,7 @@ var HttpBackendExecutor = class {
98
96
  }
99
97
  async call(handler, request) {
100
98
  const timeoutMs = handler.timeoutMs ?? DEFAULT_BACKEND_TIMEOUT_MS;
101
- const inputRecord = isPlainObject2(request.input) ? request.input : {};
99
+ const inputRecord = isRecord(request.input) ? request.input : {};
102
100
  const context = { requestId: request.requestId, resourceId: request.resourceId };
103
101
  const parts = buildBackendRequestParts(handler, inputRecord, context);
104
102
  let target;
@@ -111,26 +109,17 @@ var HttpBackendExecutor = class {
111
109
  cause: error
112
110
  });
113
111
  }
114
- const literalPrefix = handler.url.split("{")[0] ?? handler.url;
115
- try {
116
- const literalPrefixPathname = new URL(literalPrefix).pathname;
117
- if (!target.pathname.startsWith(literalPrefixPathname)) {
118
- throw new CommerceError(
119
- "INPUT_INVALID",
120
- "Path parameters resolved outside the configured backend path",
121
- {
122
- requestId: request.requestId,
123
- resourceId: request.resourceId
124
- }
125
- );
126
- }
127
- } catch (error) {
128
- if (error instanceof CommerceError) throw error;
112
+ const prefixPathname = literalPrefixPathname(handler);
113
+ if (prefixPathname !== void 0 && !target.pathname.startsWith(prefixPathname)) {
114
+ throw new CommerceError(
115
+ "INPUT_INVALID",
116
+ "Path parameters resolved outside the configured backend path",
117
+ context
118
+ );
129
119
  }
130
- const headers = { ...handler.headers ?? {} };
120
+ const headers = buildHeaders(handler, context);
131
121
  if (request.idempotencyKey !== void 0) {
132
- deleteHeader(headers, IDEMPOTENCY_KEY_HEADER);
133
- headers[IDEMPOTENCY_KEY_HEADER] = request.idempotencyKey;
122
+ setHeader(headers, IDEMPOTENCY_KEY_HEADER, request.idempotencyKey, context);
134
123
  }
135
124
  let body;
136
125
  checkQueryCollision(target, parts.query, context);
@@ -139,9 +128,7 @@ var HttpBackendExecutor = class {
139
128
  }
140
129
  if (parts.body !== void 0) {
141
130
  body = JSON.stringify(parts.body.value);
142
- if (!hasHeader(headers, "content-type")) {
143
- headers["content-type"] = "application/json";
144
- }
131
+ if (!headers.has("content-type")) headers.set("content-type", "application/json");
145
132
  }
146
133
  const started = performance.now();
147
134
  let response;
@@ -187,13 +174,13 @@ var HttpBackendExecutor = class {
187
174
  }
188
175
  );
189
176
  }
190
- const responseHeaders = headersToRecord(response.headers);
177
+ const responseHeaders = Object.fromEntries(response.headers);
191
178
  const contentType = response.headers.get("content-type") ?? "";
192
179
  let parsedBody;
193
180
  try {
194
181
  parsedBody = await parseBody(response, contentType);
195
182
  } catch (error) {
196
- const tooLarge = error instanceof Error && error.message.startsWith("response-body-too-large");
183
+ const tooLarge = isCommerceError(error);
197
184
  throw new CommerceError(
198
185
  "BACKEND_ERROR",
199
186
  tooLarge ? "Backend response exceeded the maximum allowed size" : "Backend response could not be read",
@@ -240,8 +227,9 @@ function findUnparsedBraceToken(url) {
240
227
  return token?.[0] ?? residue[index];
241
228
  }
242
229
  function validateBackendRequestShape(handler, input, context) {
243
- const inputRecord = isPlainObject2(input) ? input : {};
230
+ const inputRecord = isRecord(input) ? input : {};
244
231
  const parts = buildBackendRequestParts(handler, inputRecord, context);
232
+ buildHeaders(handler, context);
245
233
  let target;
246
234
  try {
247
235
  target = new URL(parts.url);
@@ -269,7 +257,7 @@ function buildBackendRequestParts(handler, input, context) {
269
257
  function resolveBoundGroup(input, key, kind, context) {
270
258
  const value = input[key];
271
259
  if (value === void 0) return {};
272
- if (!isPlainObject2(value)) {
260
+ if (!isRecord(value)) {
273
261
  throw new CommerceError(
274
262
  "INPUT_INVALID",
275
263
  `Input "${key}" must be an object of ${kind} parameters`,
@@ -290,14 +278,11 @@ function checkQueryCollision(target, remaining, context) {
290
278
  }
291
279
  }
292
280
  }
293
- function isPlainObject2(value) {
294
- return typeof value === "object" && value !== null && !Array.isArray(value);
295
- }
296
281
  var TRAVERSAL_PATH_VALUES = /* @__PURE__ */ new Set(["", ".", ".."]);
297
282
  function applyPathTemplate(template, input, context) {
298
283
  const remaining = { ...input };
299
284
  let missing;
300
- let invalid;
285
+ let invalid2;
301
286
  const url = template.replace(PATH_PARAM_PATTERN, (_match, key) => {
302
287
  if (!Object.hasOwn(remaining, key)) {
303
288
  missing = key;
@@ -307,7 +292,7 @@ function applyPathTemplate(template, input, context) {
307
292
  delete remaining[key];
308
293
  const raw = stringifyPrimitive(value);
309
294
  if (TRAVERSAL_PATH_VALUES.has(raw)) {
310
- invalid = key;
295
+ invalid2 = key;
311
296
  return "";
312
297
  }
313
298
  return encodeURIComponent(raw);
@@ -318,10 +303,10 @@ function applyPathTemplate(template, input, context) {
318
303
  details: { field: missing }
319
304
  });
320
305
  }
321
- if (invalid !== void 0) {
322
- throw new CommerceError("INPUT_INVALID", `Path parameter "${invalid}" is not a valid value`, {
306
+ if (invalid2 !== void 0) {
307
+ throw new CommerceError("INPUT_INVALID", `Path parameter "${invalid2}" is not a valid value`, {
323
308
  ...context,
324
- details: { field: invalid }
309
+ details: { field: invalid2 }
325
310
  });
326
311
  }
327
312
  return { url, remaining };
@@ -336,22 +321,38 @@ function stringifyPrimitive(value) {
336
321
  return String(value);
337
322
  }
338
323
  }
339
- function hasHeader(headers, name) {
340
- const target = name.toLowerCase();
341
- return Object.keys(headers).some((key) => key.toLowerCase() === target);
324
+ function buildHeaders(handler, context) {
325
+ try {
326
+ return new Headers(handler.headers);
327
+ } catch {
328
+ throw invalidHeaderError(context);
329
+ }
342
330
  }
343
- function deleteHeader(headers, name) {
344
- const target = name.toLowerCase();
345
- for (const key of Object.keys(headers)) {
346
- if (key.toLowerCase() === target) delete headers[key];
331
+ function setHeader(headers, name, value, context) {
332
+ try {
333
+ headers.set(name, value);
334
+ } catch {
335
+ throw invalidHeaderError(context);
347
336
  }
348
337
  }
349
- function headersToRecord(headers) {
350
- const record = {};
351
- headers.forEach((value, key) => {
352
- record[key] = value;
338
+ function invalidHeaderError(context) {
339
+ return new CommerceError("BACKEND_ERROR", "Backend request headers are invalid", {
340
+ ...context,
341
+ details: { reason: "invalid-header" }
353
342
  });
354
- return record;
343
+ }
344
+ var prefixPathnames = /* @__PURE__ */ new WeakMap();
345
+ function literalPrefixPathname(handler) {
346
+ let cached = prefixPathnames.get(handler);
347
+ if (cached === void 0) {
348
+ try {
349
+ cached = new URL(handler.url.split("{")[0] ?? handler.url).pathname;
350
+ } catch {
351
+ cached = null;
352
+ }
353
+ prefixPathnames.set(handler, cached);
354
+ }
355
+ return cached ?? void 0;
355
356
  }
356
357
  async function readBodyCapped(response, maxBytes) {
357
358
  if (!response.body) return "";
@@ -365,7 +366,7 @@ async function readBodyCapped(response, maxBytes) {
365
366
  total += value.byteLength;
366
367
  if (total > maxBytes) {
367
368
  await reader.cancel();
368
- throw new Error(`response-body-too-large:${maxBytes}`);
369
+ throw new CommerceError("BACKEND_ERROR", `response body exceeded ${maxBytes} bytes`);
369
370
  }
370
371
  chunks.push(value);
371
372
  }
@@ -400,47 +401,6 @@ function safeStringify(value) {
400
401
  function isAbortError(error) {
401
402
  return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
402
403
  }
403
-
404
- // src/core/execution/event-bus.ts
405
- function createEventBus(options) {
406
- const subscribers = /* @__PURE__ */ new Set();
407
- return {
408
- async emit(event) {
409
- try {
410
- await options.store.appendEvent(event);
411
- } catch (error) {
412
- options.logger.error(
413
- { err: describeError(error), eventType: event.type, requestId: event.requestId },
414
- "Failed to persist commerce event"
415
- );
416
- }
417
- for (const subscriber of subscribers) {
418
- try {
419
- subscriber(event);
420
- } catch (error) {
421
- options.logger.error(
422
- { err: describeError(error), eventType: event.type, requestId: event.requestId },
423
- "Event subscriber threw"
424
- );
425
- }
426
- }
427
- },
428
- subscribe(fn) {
429
- subscribers.add(fn);
430
- return () => {
431
- subscribers.delete(fn);
432
- };
433
- }
434
- };
435
- }
436
- function describeError(error) {
437
- if (error instanceof Error) {
438
- return { message: error.message, name: error.name };
439
- }
440
- return { message: String(error) };
441
- }
442
-
443
- // src/core/execution/validation.ts
444
404
  var SUPPORTED_TYPES = /* @__PURE__ */ new Set([
445
405
  "object",
446
406
  "string",
@@ -457,13 +417,10 @@ function compileJsonSchema(schema) {
457
417
  return errors.length === 0 ? { valid: true, value: input } : { valid: false, errors };
458
418
  };
459
419
  }
460
- function isPlainObject3(value) {
461
- return typeof value === "object" && value !== null && !Array.isArray(value);
462
- }
463
420
  function matchesType(type, value) {
464
421
  switch (type) {
465
422
  case "object":
466
- return isPlainObject3(value);
423
+ return isRecord(value);
467
424
  case "array":
468
425
  return Array.isArray(value);
469
426
  case "string":
@@ -486,15 +443,7 @@ function describeType(value) {
486
443
  return typeof value;
487
444
  }
488
445
  function deepEqual(a, b) {
489
- if (a === b) return true;
490
- if (typeof a !== typeof b) return false;
491
- if (a === null || b === null) return false;
492
- if (typeof a !== "object") return false;
493
- try {
494
- return JSON.stringify(a) === JSON.stringify(b);
495
- } catch {
496
- return false;
497
- }
446
+ return a === b || isDeepStrictEqual(a, b);
498
447
  }
499
448
  function own(obj, key) {
500
449
  return Object.hasOwn(obj, key) ? obj[key] : void 0;
@@ -514,8 +463,8 @@ function isObjectSchemaNode(schema) {
514
463
  function validateNode(schema, value, path2) {
515
464
  const errors = [];
516
465
  const types = typeList(own(schema, "type"));
517
- const recognisedTypes = types?.filter((entry) => SUPPORTED_TYPES.has(entry));
518
- if (types !== void 0 && recognisedTypes !== void 0 && recognisedTypes.length === types.length && types.length > 0) {
466
+ const recognizedTypes = types?.filter((entry) => SUPPORTED_TYPES.has(entry));
467
+ if (types !== void 0 && recognizedTypes !== void 0 && recognizedTypes.length === types.length && types.length > 0) {
519
468
  if (!types.some((entry) => matchesType(entry, value))) {
520
469
  errors.push({
521
470
  path: path2,
@@ -531,7 +480,7 @@ function validateNode(schema, value, path2) {
531
480
  errors.push({ path: path2, message: `must be one of ${JSON.stringify(allowed)}` });
532
481
  }
533
482
  }
534
- if (isObjectSchemaNode(schema) && isPlainObject3(value)) {
483
+ if (isObjectSchemaNode(schema) && isRecord(value)) {
535
484
  errors.push(...validateObject(schema, value, path2));
536
485
  } else if ((types?.includes("array") ?? false) && Array.isArray(value)) {
537
486
  errors.push(...validateArray(schema, value, path2));
@@ -541,7 +490,7 @@ function validateNode(schema, value, path2) {
541
490
  function validateObject(schema, value, path2) {
542
491
  const errors = [];
543
492
  const propsRaw = own(schema, "properties");
544
- const properties = isPlainObject3(propsRaw) ? propsRaw : {};
493
+ const properties = isRecord(propsRaw) ? propsRaw : {};
545
494
  const requiredRaw = own(schema, "required");
546
495
  const required = Array.isArray(requiredRaw) ? requiredRaw.filter((entry) => typeof entry === "string") : [];
547
496
  for (const key of required) {
@@ -553,14 +502,14 @@ function validateObject(schema, value, path2) {
553
502
  for (const key of Object.keys(value)) {
554
503
  const propSchemaRaw = own(properties, key);
555
504
  if (propSchemaRaw !== void 0) {
556
- if (isPlainObject3(propSchemaRaw)) {
505
+ if (isRecord(propSchemaRaw)) {
557
506
  errors.push(...validateNode(propSchemaRaw, value[key], `${path2}.${key}`));
558
507
  }
559
508
  continue;
560
509
  }
561
510
  if (additional === false) {
562
511
  errors.push({ path: `${path2}.${key}`, message: "additional property not allowed" });
563
- } else if (isPlainObject3(additional)) {
512
+ } else if (isRecord(additional)) {
564
513
  errors.push(...validateNode(additional, value[key], `${path2}.${key}`));
565
514
  }
566
515
  }
@@ -569,7 +518,7 @@ function validateObject(schema, value, path2) {
569
518
  function validateArray(schema, value, path2) {
570
519
  const errors = [];
571
520
  const itemsRaw = own(schema, "items");
572
- if (isPlainObject3(itemsRaw)) {
521
+ if (isRecord(itemsRaw)) {
573
522
  value.forEach((item, index) => {
574
523
  errors.push(...validateNode(itemsRaw, item, `${path2}[${index}]`));
575
524
  });
@@ -592,7 +541,7 @@ function createExecutionPipeline(options) {
592
541
  await options.events.emit(event);
593
542
  } catch (error) {
594
543
  options.logger.error(
595
- { err: describeError2(error), eventType: event.type, requestId: event.requestId },
544
+ { err: describeError(error), eventType: event.type, requestId: event.requestId },
596
545
  "Event sink failed; commerce flow continues"
597
546
  );
598
547
  }
@@ -602,7 +551,7 @@ function createExecutionPipeline(options) {
602
551
  await op();
603
552
  } catch (error) {
604
553
  options.logger.error(
605
- { err: describeError2(error), requestId },
554
+ { err: describeError(error), requestId },
606
555
  `Persistence failed: ${label}`
607
556
  );
608
557
  }
@@ -689,7 +638,7 @@ function createExecutionPipeline(options) {
689
638
  await provider[action](verification.reservationId, context);
690
639
  } catch (error) {
691
640
  options.logger.error(
692
- { err: describeError2(error), requestId: request.requestId, action },
641
+ { err: describeError(error), requestId: request.requestId, action },
693
642
  "Authorization finalization failed; the reservation stays reserved"
694
643
  );
695
644
  }
@@ -699,7 +648,7 @@ function createExecutionPipeline(options) {
699
648
  async function execute(request) {
700
649
  const pipelineStart = options.clock.monotonicMs();
701
650
  const resource = options.resources.get(request.resourceId);
702
- if (!resource || !resource.exposedVia.includes(request.protocol)) {
651
+ if (!resource?.exposedVia.includes(request.protocol)) {
703
652
  throw new CommerceError(
704
653
  "RESOURCE_NOT_FOUND",
705
654
  `Resource "${request.resourceId}" was not found`,
@@ -1170,22 +1119,16 @@ function pickProvider(providers, methods) {
1170
1119
  }
1171
1120
  return void 0;
1172
1121
  }
1173
- var RESERVED_INPUT_KEYS = /* @__PURE__ */ new Set([...RESERVED_INPUT_FIELDS, "__proto__"]);
1122
+ var RESERVED_INPUT_KEYS = [...RESERVED_INPUT_FIELDS, "__proto__"];
1174
1123
  function stripReservedFields(input) {
1175
- if (typeof input !== "object" || input === null || Array.isArray(input)) return input;
1176
- const record = input;
1177
- const hasReserved = [...RESERVED_INPUT_KEYS].some((key) => Object.hasOwn(record, key));
1178
- if (!hasReserved) return input;
1124
+ if (!isRecord(input)) return input;
1125
+ if (!RESERVED_INPUT_KEYS.some((key) => Object.hasOwn(input, key))) return input;
1179
1126
  const rest = {};
1180
- for (const key of Object.keys(record)) {
1181
- if (!RESERVED_INPUT_KEYS.has(key)) rest[key] = record[key];
1127
+ for (const key of Object.keys(input)) {
1128
+ if (!RESERVED_INPUT_KEYS.includes(key)) rest[key] = input[key];
1182
1129
  }
1183
1130
  return rest;
1184
1131
  }
1185
- function describeError2(error) {
1186
- if (error instanceof Error) return { message: error.message, name: error.name };
1187
- return { message: String(error) };
1188
- }
1189
1132
 
1190
1133
  // src/core/execution/registry.ts
1191
1134
  var ResourceRegistryImpl = class {
@@ -1219,6 +1162,22 @@ function createResourceRegistry(resources) {
1219
1162
  return new ResourceRegistryImpl(resources);
1220
1163
  }
1221
1164
 
1165
+ // src/core/execution/store-event-sink.ts
1166
+ function createStoreEventSink(options) {
1167
+ return {
1168
+ async emit(event) {
1169
+ try {
1170
+ await options.store.appendEvent(event);
1171
+ } catch (error) {
1172
+ options.logger.error(
1173
+ { err: describeError(error), eventType: event.type, requestId: event.requestId },
1174
+ "Failed to persist commerce event"
1175
+ );
1176
+ }
1177
+ }
1178
+ };
1179
+ }
1180
+
1222
1181
  // src/protocols/acp/constants.ts
1223
1182
  var ACP_SPEC_VERSION = "2026-04-17";
1224
1183
  var ACP_API_VERSION = "2026-04-17";
@@ -1237,6 +1196,9 @@ var ACP_OPERATION_INPUT_KEYS = {
1237
1196
  completeCheckoutSession: ["path", "body"],
1238
1197
  cancelCheckoutSession: ["path"]
1239
1198
  };
1199
+ var ACP_OPERATION_OPTIONAL_INPUT_KEYS = {
1200
+ cancelCheckoutSession: ["body"]
1201
+ };
1240
1202
  var ACP_JSON_MEDIA_TYPE = "application/json";
1241
1203
  var ACP_API_VERSION_HEADER = "api-version";
1242
1204
  var ACP_REQUEST_ID_HEADER = "request-id";
@@ -1249,7 +1211,6 @@ var ACP_MAX_REQUEST_ID_LENGTH = 128;
1249
1211
  var SUPPORTED_CONFIG_VERSION = 1;
1250
1212
  var ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
1251
1213
  var ZERO_ADDRESS_PATTERN = /^0x0{40}$/i;
1252
- var MCP_TOOL_NAME_PATTERN = /^[A-Za-z0-9._-]{1,128}$/;
1253
1214
  var NumberOrString = z.union([z.number(), z.string()]);
1254
1215
  var BooleanOrString = z.union([z.boolean(), z.string()]);
1255
1216
  var MerchantSchema = z.object({
@@ -1260,14 +1221,14 @@ var MerchantSchema = z.object({
1260
1221
  var ServerSchema = z.object({
1261
1222
  port: NumberOrString,
1262
1223
  host: z.string().min(1),
1263
- /** Shared secret gating /api/receipts, /api/events, /api/events/stream. No token -> those routes 404. */
1224
+ // Shared secret gating /api/receipts and /api/events. Without it both
1225
+ // routes 404.
1264
1226
  adminToken: z.string().min(1).optional(),
1265
- /** Browser origins allowed to read the dashboard-facing routes. Empty by default: closed. */
1266
- // Entries are matched literally against the browser's
1267
- // `Origin` header, so `"*"` and a trailing slash match nothing at all -
1268
- // fail-closed (a lockout, not a bypass), but silently, and a lockout with
1269
- // no explanation is the kind of thing an operator "fixes" by disabling the
1270
- // check. Reject those two shapes with a pointer instead.
1227
+ // Browser origins allowed to call the gateway; none by default. A request
1228
+ // with any other `Origin` gets 403 on every route. Entries match the
1229
+ // `Origin` header exactly (ignoring case), so `"*"` and a trailing slash
1230
+ // would match nothing: a silent lockout that an operator might "fix" by
1231
+ // disabling the check. Both are refused with a pointer.
1271
1232
  allowedOrigins: z.array(
1272
1233
  z.string().min(1).refine((origin) => origin !== "*", {
1273
1234
  message: `wildcard "*" is not supported - allowedOrigins entries are matched literally against the browser's Origin header, so "*" would match nothing. List each scheme://host[:port] explicitly.`
@@ -1286,16 +1247,14 @@ var RESERVED_GATEWAY_PATHS = [
1286
1247
  "/health",
1287
1248
  "/ready",
1288
1249
  "/.well-known/agent-commerce",
1289
- // Fixed by the A2A specification, so it is the adapter's to serve and never
1290
- // a configurable mount's to claim.
1250
+ // Fixed by the A2A specification and served by the adapter
1291
1251
  "/.well-known/agent-card.json",
1292
- // Likewise fixed by the ACP specification.
1252
+ // Fixed by the ACP specification
1293
1253
  ACP_WELL_KNOWN_PATH,
1294
1254
  "/api/resources",
1295
1255
  "/api/resources/:id/invoke",
1296
1256
  "/api/receipts",
1297
- "/api/events",
1298
- "/api/events/stream"
1257
+ "/api/events"
1299
1258
  ];
1300
1259
  var MountPathSchema = z.string().min(1).refine((value) => value.startsWith("/"), {
1301
1260
  message: 'must start with "/" - it is an absolute gateway path, e.g. "/mcp".'
@@ -1327,16 +1286,14 @@ var ProtocolsSchema = z.object({
1327
1286
  enabled: BooleanOrString,
1328
1287
  mountPath: MountPathSchema
1329
1288
  }).strict(),
1330
- // Optional block: A2A is off unless an operator asks for it, and an
1331
- // existing config predating the adapter stays valid.
1289
+ // Optional; A2A is off unless configured
1332
1290
  a2a: z.object({
1333
1291
  enabled: BooleanOrString,
1334
1292
  mountPath: MountPathSchema.optional()
1335
1293
  }).strict().optional(),
1336
- // Likewise optional and off by default. The sub-blocks are optional here
1337
- // and required by the business pass only when ACP is enabled, so a
1338
- // disabled placeholder block stays writable and an enabled one gets a
1339
- // message naming the piece it is missing.
1294
+ // Optional and off by default. The business pass requires the sub-blocks
1295
+ // only when ACP is enabled, so a disabled placeholder stays valid and an
1296
+ // enabled block gets a message naming what it lacks.
1340
1297
  acp: z.object({
1341
1298
  enabled: BooleanOrString,
1342
1299
  mountPath: MountPathSchema.optional(),
@@ -1392,7 +1349,7 @@ var FacilitatorAuthSchema = z.discriminatedUnion("type", [
1392
1349
  z.object({ type: z.literal("none") }).strict(),
1393
1350
  z.object({ type: z.literal("bearer"), token: z.string().min(1) }).strict(),
1394
1351
  // Coinbase Developer Platform. Needs the optional peer `@coinbase/x402`,
1395
- // which signs a fresh JWT per request; a static header cannot express it.
1352
+ // which signs a fresh JWT per request.
1396
1353
  z.object({
1397
1354
  type: z.literal("cdp"),
1398
1355
  apiKeyId: z.string().min(1),
@@ -1407,10 +1364,8 @@ var FacilitatorSchema = z.discriminatedUnion("mode", [
1407
1364
  z.object({
1408
1365
  mode: z.literal("remote"),
1409
1366
  url: z.string().min(1),
1410
- // Absent means "this facilitator takes no credential" - an explicit
1411
- // statement, normalised to `{ type: 'none' }` below. On a mainnet that
1412
- // combination is refused outright, so the default can never quietly
1413
- // become an unauthenticated production facilitator.
1367
+ // Absent auth sends no credential and normalizes to `{ type: 'none' }`,
1368
+ // which on mainnet requires `allowUnauthenticatedFacilitator`
1414
1369
  auth: FacilitatorAuthSchema.optional()
1415
1370
  }).strict()
1416
1371
  ]);
@@ -1425,9 +1380,9 @@ var X402Schema = z.object({
1425
1380
  payTo: z.string().min(1),
1426
1381
  maxTimeoutSeconds: NumberOrString,
1427
1382
  facilitator: FacilitatorSchema,
1428
- /** Real funds. Never defaulted - see src/payments/x402/guardrails.ts. */
1383
+ // Real funds. Never defaulted; see src/payments/x402/guardrails.ts
1429
1384
  allowMainnet: BooleanOrString.optional(),
1430
- /** Accepts a mainnet facilitator that takes no credential. Never defaulted. */
1385
+ // Accepts credential-free facilitator access on mainnet. Never defaulted
1431
1386
  allowUnauthenticatedFacilitator: BooleanOrString.optional()
1432
1387
  }).strict();
1433
1388
  var MppSchema = z.object({
@@ -1442,9 +1397,9 @@ var MppSchema = z.object({
1442
1397
  challengeSecret: z.string().min(1),
1443
1398
  challengeTtlSeconds: NumberOrString.optional(),
1444
1399
  facilitator: FacilitatorSchema,
1445
- // Real funds. Never defaulted - see src/payments/x402/guardrails.ts
1400
+ // Real funds. Never defaulted; see src/payments/x402/guardrails.ts
1446
1401
  allowMainnet: BooleanOrString.optional(),
1447
- // Accepts a mainnet facilitator that takes no credential. Never defaulted
1402
+ // Accepts credential-free facilitator access on mainnet. Never defaulted
1448
1403
  allowUnauthenticatedFacilitator: BooleanOrString.optional()
1449
1404
  }).strict();
1450
1405
  var PaymentsSchema = z.object({
@@ -1457,11 +1412,8 @@ var Ap2KeySchema = z.object({
1457
1412
  }).strict();
1458
1413
  var Ap2IssuerSchema = z.object({
1459
1414
  issuer: z.string().min(1),
1460
- /**
1461
- * Required, not defaulted. Without it, a mandate minted for another
1462
- * merchant would verify here, and there is no value worth guessing for
1463
- * something that decides that.
1464
- */
1415
+ // Required, not defaulted: without it a mandate minted for another
1416
+ // merchant would verify here
1465
1417
  audience: z.string().min(1),
1466
1418
  keys: z.array(Ap2KeySchema).min(1)
1467
1419
  }).strict();
@@ -1488,7 +1440,7 @@ var RawConfigSchema = z.object({
1488
1440
  authorization: AuthorizationSchema.optional()
1489
1441
  }).strict();
1490
1442
  function parseConfig(raw, env) {
1491
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1443
+ if (!isRecord(raw)) {
1492
1444
  throw new CommerceError(
1493
1445
  "CONFIG_INVALID",
1494
1446
  'Configuration root must be a mapping (object) at "$"',
@@ -1500,7 +1452,7 @@ function parseConfig(raw, env) {
1500
1452
  checkVersion(raw);
1501
1453
  const substituted = substituteEnv(raw, env);
1502
1454
  const parsed = parseWithZod(RawConfigSchema, substituted);
1503
- return normalise(parsed);
1455
+ return normalize(parsed);
1504
1456
  }
1505
1457
  function checkVersion(raw) {
1506
1458
  if (!("version" in raw)) {
@@ -1508,12 +1460,12 @@ function checkVersion(raw) {
1508
1460
  details: { path: "version" }
1509
1461
  });
1510
1462
  }
1511
- const rawVersion = raw["version"];
1512
- const version = typeof rawVersion === "string" ? Number(rawVersion) : rawVersion;
1463
+ const version = raw["version"];
1513
1464
  if (version !== SUPPORTED_CONFIG_VERSION) {
1465
+ const hint = typeof version === "string" ? ", written as a number without quotes" : "";
1514
1466
  throw new CommerceError(
1515
1467
  "CONFIG_INVALID",
1516
- `Unsupported config version "${String(rawVersion)}": this gateway only supports version ${SUPPORTED_CONFIG_VERSION}`,
1468
+ `Unsupported config version "${String(version)}": this gateway only supports version ${SUPPORTED_CONFIG_VERSION}${hint}`,
1517
1469
  { details: { path: "version", supported: SUPPORTED_CONFIG_VERSION } }
1518
1470
  );
1519
1471
  }
@@ -1587,7 +1539,7 @@ function toBoolean(value, path2) {
1587
1539
  var SUPPORTED_PROTOCOLS = new Set(PROTOCOL_NAMES);
1588
1540
  var SUPPORTED_PAYMENT_METHODS = new Set(PAYMENT_METHOD_NAMES);
1589
1541
  var SUPPORTED_AUTHORIZATION_METHODS = /* @__PURE__ */ new Set(["ap2"]);
1590
- function normalise(raw) {
1542
+ function normalize(raw) {
1591
1543
  const protocols = {
1592
1544
  http: { enabled: toBoolean(raw.protocols.http.enabled, "protocols.http.enabled") },
1593
1545
  mcp: {
@@ -1598,7 +1550,7 @@ function normalise(raw) {
1598
1550
  enabled: toBoolean(raw.protocols.a2a?.enabled ?? false, "protocols.a2a.enabled"),
1599
1551
  mountPath: raw.protocols.a2a?.mountPath ?? DEFAULT_A2A_MOUNT_PATH
1600
1552
  },
1601
- acp: normaliseAcp(raw.protocols.acp)
1553
+ acp: normalizeAcp(raw.protocols.acp)
1602
1554
  };
1603
1555
  validateMountPaths(protocols);
1604
1556
  const x402Raw = raw.payments.x402;
@@ -1621,7 +1573,7 @@ function normalise(raw) {
1621
1573
  min: 1
1622
1574
  }
1623
1575
  ),
1624
- facilitator: normaliseFacilitator(x402Raw.facilitator),
1576
+ facilitator: normalizeFacilitator(x402Raw.facilitator),
1625
1577
  ...x402Raw.allowMainnet !== void 0 ? { allowMainnet: toBoolean(x402Raw.allowMainnet, "payments.x402.allowMainnet") } : {},
1626
1578
  ...x402Raw.allowUnauthenticatedFacilitator !== void 0 ? {
1627
1579
  allowUnauthenticatedFacilitator: toBoolean(
@@ -1644,13 +1596,13 @@ function normalise(raw) {
1644
1596
  ...x402.allowUnauthenticatedFacilitator !== void 0 ? { allowUnauthenticatedFacilitator: x402.allowUnauthenticatedFacilitator } : {}
1645
1597
  });
1646
1598
  }
1647
- const mpp = normaliseMpp(raw.payments.mpp);
1648
- const ap2 = normaliseAp2(raw.authorization?.ap2);
1599
+ const mpp = normalizeMpp(raw.payments.mpp);
1600
+ const ap2 = normalizeAp2(raw.authorization?.ap2);
1649
1601
  if (ap2?.enabled) {
1650
1602
  validateReplayStoreIsolated(ap2.replay.path, raw.storage.receipts.path, protocols.acp);
1651
1603
  }
1652
1604
  const resources = Object.entries(raw.resources).map(
1653
- ([id, entry]) => normaliseResource(id, entry, protocols, x402, mpp, ap2)
1605
+ ([id, entry]) => normalizeResource(id, entry, protocols, x402, mpp, ap2)
1654
1606
  );
1655
1607
  if (protocols.acp.enabled) validateAcpCheckoutMapping(protocols.acp, resources);
1656
1608
  return {
@@ -1660,8 +1612,7 @@ function normalise(raw) {
1660
1612
  name: raw.merchant.name,
1661
1613
  publicBaseUrl: raw.merchant.publicBaseUrl
1662
1614
  },
1663
- // min 0, not 1: port 0 is the standard "let the OS pick a free port"
1664
- // convention, useful for tests and ephemeral dev/demo instances.
1615
+ // Port 0 lets the OS pick a free port, for tests and throwaway instances
1665
1616
  server: {
1666
1617
  port: toNumber(raw.server.port, "server.port", { min: 0, max: 65535 }),
1667
1618
  host: raw.server.host,
@@ -1701,7 +1652,7 @@ function validateMountPaths(protocols) {
1701
1652
  function ap2Invalid(path2, message, extra = {}) {
1702
1653
  return new CommerceError("CONFIG_INVALID", message, { details: { path: path2, ...extra } });
1703
1654
  }
1704
- function normaliseAp2(raw) {
1655
+ function normalizeAp2(raw) {
1705
1656
  if (raw === void 0) return void 0;
1706
1657
  if (!toBoolean(raw.enabled, "authorization.ap2.enabled")) return { enabled: false };
1707
1658
  const specVersion = raw.specVersion ?? AP2_SPEC_VERSION;
@@ -1721,14 +1672,14 @@ function normaliseAp2(raw) {
1721
1672
  if (raw.replay === void 0) {
1722
1673
  throw ap2Invalid(
1723
1674
  "authorization.ap2.replay",
1724
- "authorization.ap2.replay is required when AP2 is enabled - it names the SQLite file recording which mandates have been spent, and without it a verified mandate could authorise a second settlement"
1675
+ "authorization.ap2.replay is required when AP2 is enabled - it names the SQLite file recording which mandates have been spent, and without it a verified mandate could authorize a second settlement"
1725
1676
  );
1726
1677
  }
1727
- const mandateIssuers = normaliseIssuers(
1678
+ const mandateIssuers = normalizeIssuers(
1728
1679
  raw.trust?.mandateIssuers,
1729
1680
  "authorization.ap2.trust.mandateIssuers"
1730
1681
  );
1731
- const checkoutIssuers = normaliseIssuers(
1682
+ const checkoutIssuers = normalizeIssuers(
1732
1683
  raw.trust?.checkoutIssuers,
1733
1684
  "authorization.ap2.trust.checkoutIssuers"
1734
1685
  );
@@ -1745,7 +1696,7 @@ function normaliseAp2(raw) {
1745
1696
  replay: { path: raw.replay.path }
1746
1697
  };
1747
1698
  }
1748
- function normaliseIssuers(raw, path2) {
1699
+ function normalizeIssuers(raw, path2) {
1749
1700
  if (raw === void 0 || raw.length === 0) {
1750
1701
  throw ap2Invalid(
1751
1702
  path2,
@@ -1831,11 +1782,11 @@ function validateJwk(jwk, kid, path2) {
1831
1782
  if (jwk["kid"] !== void 0 && value("kid") !== kid) {
1832
1783
  fail(`declares kid "${value("kid")}", which disagrees with the configured kid "${kid}"`);
1833
1784
  }
1834
- const normalised = {};
1785
+ const normalized = {};
1835
1786
  for (const member of AP2_JWK_MEMBERS) {
1836
- if (jwk[member] !== void 0) normalised[member] = value(member);
1787
+ if (jwk[member] !== void 0) normalized[member] = value(member);
1837
1788
  }
1838
- return normalised;
1789
+ return normalized;
1839
1790
  }
1840
1791
  function validateReplayStoreIsolated(replayPath, receiptsPath, acp) {
1841
1792
  if (replayPath === ":memory:") return;
@@ -1849,7 +1800,7 @@ function validateReplayStoreIsolated(replayPath, receiptsPath, acp) {
1849
1800
  );
1850
1801
  }
1851
1802
  }
1852
- function normaliseResourceAuthorization(id, entry, pricing, ap2) {
1803
+ function normalizeResourceAuthorization(id, entry, pricing, ap2) {
1853
1804
  const required = entry.authorization?.required;
1854
1805
  if (required === void 0) return void 0;
1855
1806
  const path2 = `resources.${id}.authorization.required`;
@@ -1889,7 +1840,7 @@ function normaliseResourceAuthorization(id, entry, pricing, ap2) {
1889
1840
  function acpInvalid(path2, message, extra = {}) {
1890
1841
  return new CommerceError("CONFIG_INVALID", message, { details: { path: path2, ...extra } });
1891
1842
  }
1892
- function normaliseAcp(raw) {
1843
+ function normalizeAcp(raw) {
1893
1844
  const mountPath = raw?.mountPath ?? DEFAULT_ACP_MOUNT_PATH;
1894
1845
  if (raw === void 0 || !toBoolean(raw.enabled, "protocols.acp.enabled")) {
1895
1846
  return { enabled: false, mountPath };
@@ -1919,9 +1870,8 @@ function normaliseAcp(raw) {
1919
1870
  { min: ACP_RETENTION_HOURS }
1920
1871
  )
1921
1872
  },
1922
- checkout: { operations: normaliseAcpOperations(raw.checkout) },
1923
- // Built key by key rather than passed through: absent metadata must stay
1924
- // absent from the discovery document, not appear there as `undefined`.
1873
+ checkout: { operations: normalizeAcpOperations(raw.checkout) },
1874
+ // Built key by key so absent metadata stays absent rather than `undefined`
1925
1875
  ...discovery !== void 0 ? {
1926
1876
  discovery: {
1927
1877
  ...discovery.documentationUrl !== void 0 ? { documentationUrl: discovery.documentationUrl } : {},
@@ -1932,7 +1882,7 @@ function normaliseAcp(raw) {
1932
1882
  } : {}
1933
1883
  };
1934
1884
  }
1935
- function normaliseAcpOperations(checkout) {
1885
+ function normalizeAcpOperations(checkout) {
1936
1886
  const configured = checkout?.operations ?? {};
1937
1887
  for (const key of Object.keys(configured)) {
1938
1888
  if (!ACP_CHECKOUT_OPERATIONS.includes(key)) {
@@ -2000,13 +1950,15 @@ function validateAcpOperationInput(path2, operation, resource) {
2000
1950
  const schema = resource.inputSchema;
2001
1951
  if (schema === void 0) return;
2002
1952
  const keys = ACP_OPERATION_INPUT_KEYS[operation];
1953
+ const optionalKeys = ACP_OPERATION_OPTIONAL_INPUT_KEYS[operation] ?? [];
2003
1954
  const properties = schema["properties"];
2004
- if (schema["additionalProperties"] === false && isPlainObject4(properties)) {
2005
- for (const key of keys) {
1955
+ if (schema["additionalProperties"] === false && isRecord(properties)) {
1956
+ for (const key of [...keys, ...optionalKeys]) {
2006
1957
  if (!Object.hasOwn(properties, key)) {
1958
+ const when = keys.includes(key) ? "always sends" : "sends when the caller supplies it";
2007
1959
  throw acpInvalid(
2008
1960
  path2,
2009
- `Resource "${resource.id}" implements ACP operation "${operation}" but its input schema sets additionalProperties: false without declaring "${key}", which the adapter always sends for this operation`,
1961
+ `Resource "${resource.id}" implements ACP operation "${operation}" but its input schema sets additionalProperties: false without declaring "${key}", which the adapter ${when} for this operation`,
2010
1962
  { resourceId: resource.id }
2011
1963
  );
2012
1964
  }
@@ -2018,17 +1970,17 @@ function validateAcpOperationInput(path2, operation, resource) {
2018
1970
  if (typeof entry === "string" && !keys.includes(entry)) {
2019
1971
  throw acpInvalid(
2020
1972
  path2,
2021
- `Resource "${resource.id}" implements ACP operation "${operation}" but its input schema requires "${entry}", which that operation never supplies (it sends: ${keys.join(", ")})`,
1973
+ `Resource "${resource.id}" implements ACP operation "${operation}" but its input schema requires "${entry}", which that operation does not always supply (it always sends: ${keys.join(", ")})`,
2022
1974
  { resourceId: resource.id }
2023
1975
  );
2024
1976
  }
2025
1977
  }
2026
1978
  }
2027
1979
  }
2028
- function normaliseFacilitator(raw) {
1980
+ function normalizeFacilitator(raw) {
2029
1981
  return raw.mode === "local" ? { mode: "local", signerPrivateKey: raw.signerPrivateKey } : { mode: "remote", url: raw.url, auth: raw.auth ?? { type: "none" } };
2030
1982
  }
2031
- function normaliseMpp(raw) {
1983
+ function normalizeMpp(raw) {
2032
1984
  if (raw === void 0) return void 0;
2033
1985
  const network = raw.network ?? MPP_DEFAULT_NETWORK;
2034
1986
  if (!isMppNetwork(network)) {
@@ -2055,7 +2007,7 @@ function normaliseMpp(raw) {
2055
2007
  { min: 1 }
2056
2008
  )
2057
2009
  } : {},
2058
- facilitator: normaliseFacilitator(raw.facilitator),
2010
+ facilitator: normalizeFacilitator(raw.facilitator),
2059
2011
  ...raw.allowMainnet !== void 0 ? { allowMainnet: toBoolean(raw.allowMainnet, "payments.mpp.allowMainnet") } : {},
2060
2012
  ...raw.allowUnauthenticatedFacilitator !== void 0 ? {
2061
2013
  allowUnauthenticatedFacilitator: toBoolean(
@@ -2092,7 +2044,7 @@ function normaliseMpp(raw) {
2092
2044
  });
2093
2045
  return mpp;
2094
2046
  }
2095
- function normaliseResource(id, entry, protocols, x402, mpp, ap2) {
2047
+ function normalizeResource(id, entry, protocols, x402, mpp, ap2) {
2096
2048
  if (entry.input !== void 0) validateResourceSchemaKeywords(id, "input", entry.input);
2097
2049
  const inputProperties = entry.input?.["properties"];
2098
2050
  if (inputProperties && typeof inputProperties === "object") {
@@ -2158,6 +2110,7 @@ function normaliseResource(id, entry, protocols, x402, mpp, ap2) {
2158
2110
  );
2159
2111
  }
2160
2112
  validateBackendUrl(id, entry.backend.url);
2113
+ validateBackendHeaders(id, entry.backend.headers);
2161
2114
  const pathScope = validateInputBindings(id, entry.backend, entry.input);
2162
2115
  validatePathParametersDeclared(id, entry.backend.url, pathScope.schema, pathScope.where);
2163
2116
  if (entry.pricing.type === "dynamic") {
@@ -2208,7 +2161,7 @@ function normaliseResource(id, entry, protocols, x402, mpp, ap2) {
2208
2161
  }
2209
2162
  }
2210
2163
  const pricing = entry.pricing.type === "free" ? { type: "free" } : { type: "fixed", amount: entry.pricing.amount, currency: entry.pricing.currency };
2211
- const authorization = normaliseResourceAuthorization(id, entry, pricing, ap2);
2164
+ const authorization = normalizeResourceAuthorization(id, entry, pricing, ap2);
2212
2165
  return {
2213
2166
  id,
2214
2167
  name: entry.name,
@@ -2238,11 +2191,11 @@ function defaultClosedObjectSchema(schema) {
2238
2191
  const result = { ...schema };
2239
2192
  if (isObjectSchema) {
2240
2193
  const properties = schema["properties"];
2241
- if (isPlainObject4(properties)) {
2194
+ if (isRecord(properties)) {
2242
2195
  result["properties"] = Object.fromEntries(
2243
2196
  Object.entries(properties).map(([key, sub]) => [
2244
2197
  key,
2245
- isPlainObject4(sub) ? defaultClosedObjectSchema(sub) : sub
2198
+ isRecord(sub) ? defaultClosedObjectSchema(sub) : sub
2246
2199
  ])
2247
2200
  );
2248
2201
  }
@@ -2251,11 +2204,11 @@ function defaultClosedObjectSchema(schema) {
2251
2204
  }
2252
2205
  }
2253
2206
  const items = schema["items"];
2254
- if (isPlainObject4(items)) {
2207
+ if (isRecord(items)) {
2255
2208
  result["items"] = defaultClosedObjectSchema(items);
2256
2209
  }
2257
2210
  const additional = schema["additionalProperties"];
2258
- if (isPlainObject4(additional)) {
2211
+ if (isRecord(additional)) {
2259
2212
  result["additionalProperties"] = defaultClosedObjectSchema(additional);
2260
2213
  }
2261
2214
  return result;
@@ -2286,9 +2239,6 @@ var UNSUPPORTED_SCHEMA_KEYWORDS = [
2286
2239
  "maxItems",
2287
2240
  "uniqueItems"
2288
2241
  ];
2289
- function isPlainObject4(value) {
2290
- return typeof value === "object" && value !== null && !Array.isArray(value);
2291
- }
2292
2242
  function excludesObjectType(schema) {
2293
2243
  const type = schema["type"];
2294
2244
  if (type === void 0 || type === "object") return false;
@@ -2313,7 +2263,7 @@ function validateResourceSchemaKeywords(id, path2, schema) {
2313
2263
  const requiredRaw = schema["required"];
2314
2264
  const closed = !Object.hasOwn(schema, "additionalProperties") ? true : schema["additionalProperties"] === false;
2315
2265
  if (Array.isArray(requiredRaw) && closed && isObjectSchemaNode(schema)) {
2316
- const declared = isPlainObject4(schema["properties"]) ? schema["properties"] : {};
2266
+ const declared = isRecord(schema["properties"]) ? schema["properties"] : {};
2317
2267
  const undeclared = requiredRaw.filter(
2318
2268
  (name) => typeof name === "string" && !Object.hasOwn(declared, name)
2319
2269
  );
@@ -2326,17 +2276,17 @@ function validateResourceSchemaKeywords(id, path2, schema) {
2326
2276
  }
2327
2277
  }
2328
2278
  const properties = schema["properties"];
2329
- if (isPlainObject4(properties)) {
2279
+ if (isRecord(properties)) {
2330
2280
  for (const [key, sub] of Object.entries(properties)) {
2331
- if (isPlainObject4(sub)) validateResourceSchemaKeywords(id, `${path2}.properties.${key}`, sub);
2281
+ if (isRecord(sub)) validateResourceSchemaKeywords(id, `${path2}.properties.${key}`, sub);
2332
2282
  }
2333
2283
  }
2334
2284
  const additional = schema["additionalProperties"];
2335
- if (isPlainObject4(additional)) {
2285
+ if (isRecord(additional)) {
2336
2286
  validateResourceSchemaKeywords(id, `${path2}.additionalProperties`, additional);
2337
2287
  }
2338
2288
  const items = schema["items"];
2339
- if (isPlainObject4(items)) {
2289
+ if (isRecord(items)) {
2340
2290
  validateResourceSchemaKeywords(id, `${path2}.items`, items);
2341
2291
  } else if (Array.isArray(items)) {
2342
2292
  console.warn(
@@ -2403,7 +2353,7 @@ function validateInputBindings(id, backend, input) {
2403
2353
  );
2404
2354
  }
2405
2355
  const seen = /* @__PURE__ */ new Map();
2406
- const properties = isPlainObject4(input?.["properties"]) ? input["properties"] : {};
2356
+ const properties = isRecord(input?.["properties"]) ? input["properties"] : {};
2407
2357
  const required = new Set(
2408
2358
  Array.isArray(input?.["required"]) ? input["required"].filter((value) => typeof value === "string") : []
2409
2359
  );
@@ -2426,7 +2376,7 @@ function validateInputBindings(id, backend, input) {
2426
2376
  );
2427
2377
  }
2428
2378
  const declared = properties[property];
2429
- if (location !== "body" && isPlainObject4(declared) && !isObjectSchemaNode(declared)) {
2379
+ if (location !== "body" && isRecord(declared) && !isObjectSchemaNode(declared)) {
2430
2380
  fail(
2431
2381
  `binds "${location}" to input property "${property}", which is not an object schema - ${location} parameters are read as an object of name/value pairs`,
2432
2382
  { location, property }
@@ -2442,7 +2392,7 @@ function validateInputBindings(id, backend, input) {
2442
2392
  if (bindings.path === void 0) return { schema: void 0, where: "its input schema" };
2443
2393
  const group = properties[bindings.path];
2444
2394
  return {
2445
- schema: isPlainObject4(group) ? group : void 0,
2395
+ schema: isRecord(group) ? group : void 0,
2446
2396
  where: `input.properties.${bindings.path}`
2447
2397
  };
2448
2398
  }
@@ -2454,7 +2404,7 @@ function validatePathParametersDeclared(id, url, input, where) {
2454
2404
  if (stray !== void 0) {
2455
2405
  throw new CommerceError(
2456
2406
  "CONFIG_INVALID",
2457
- `Resource "${id}" has backend.url containing "${stray}", which is not a valid path parameter (allowed characters: A-Z a-z 0-9 _ . -). It would be sent to the backend literally, so a paid resource would settle payment and then never reach the backend`,
2407
+ `Resource "${id}" has backend.url containing "${stray}", which is not a valid path parameter (allowed characters: A-Z a-z 0-9 _ . -). It would reach the backend as literal text, so a paid resource would take payment for a request the backend cannot serve`,
2458
2408
  { details: { path: `resources.${id}.backend.url`, resourceId: id, token: stray } }
2459
2409
  );
2460
2410
  }
@@ -2472,13 +2422,13 @@ function validatePathParametersDeclared(id, url, input, where) {
2472
2422
  if (origin === void 0 || !prefix.startsWith(`${origin}/`)) {
2473
2423
  throw new CommerceError(
2474
2424
  "CONFIG_INVALID",
2475
- `Resource "${id}" has a backend.url parameter before the end of the host ("${url}"). A caller-supplied value would choose which host the gateway calls, which is request forgery with the gateway's own network position. Parameters are supported inside the path and query only`,
2425
+ `Resource "${id}" has a backend.url parameter before the end of the host. A caller-supplied value would choose which host the gateway calls, which is request forgery with the gateway's own network position. Parameters are supported inside the path and query only`,
2476
2426
  { details: { path: `resources.${id}.backend.url`, resourceId: id } }
2477
2427
  );
2478
2428
  }
2479
2429
  }
2480
2430
  const propertiesRaw = input?.["properties"];
2481
- const properties = isPlainObject4(propertiesRaw) ? propertiesRaw : {};
2431
+ const properties = isRecord(propertiesRaw) ? propertiesRaw : {};
2482
2432
  const requiredRaw = input?.["required"];
2483
2433
  const required = new Set(
2484
2434
  Array.isArray(requiredRaw) ? requiredRaw.filter((entry) => typeof entry === "string") : []
@@ -2488,14 +2438,14 @@ function validatePathParametersDeclared(id, url, input, where) {
2488
2438
  if (!Object.hasOwn(properties, param)) {
2489
2439
  throw new CommerceError(
2490
2440
  "CONFIG_INVALID",
2491
- `Resource "${id}" has backend.url path parameter "{${param}}" which is not declared in ${where} - the caller has no way to supply it, so every call would settle payment (if priced) and then fail to reach the backend`,
2441
+ `Resource "${id}" has backend.url path parameter "{${param}}" which is not declared in ${where} - the caller has no way to supply it, so every call would be refused`,
2492
2442
  { details: { path: path2, resourceId: id, param } }
2493
2443
  );
2494
2444
  }
2495
2445
  if (!required.has(param)) {
2496
2446
  throw new CommerceError(
2497
2447
  "CONFIG_INVALID",
2498
- `Resource "${id}" has backend.url path parameter "{${param}}" declared in ${where} but not listed in its "required" - a caller that omits it hits the same unservable-request problem as an undeclared parameter`,
2448
+ `Resource "${id}" has backend.url path parameter "{${param}}" declared in ${where} but not listed in its "required" - a call that omits it would be refused`,
2499
2449
  { details: { path: path2, resourceId: id, param } }
2500
2450
  );
2501
2451
  }
@@ -2508,7 +2458,7 @@ function validateBackendUrl(id, url) {
2508
2458
  } catch {
2509
2459
  throw new CommerceError(
2510
2460
  "CONFIG_INVALID",
2511
- `Resource "${id}" has an invalid backend.url "${url}": must be an absolute http:// or https:// URL`,
2461
+ `Resource "${id}" has an invalid backend.url: must be an absolute http:// or https:// URL`,
2512
2462
  { details: { path: `resources.${id}.backend.url`, resourceId: id } }
2513
2463
  );
2514
2464
  }
@@ -2520,6 +2470,19 @@ function validateBackendUrl(id, url) {
2520
2470
  );
2521
2471
  }
2522
2472
  }
2473
+ function validateBackendHeaders(id, headers) {
2474
+ for (const [name, value] of Object.entries(headers ?? {})) {
2475
+ try {
2476
+ new Headers([[name, value]]);
2477
+ } catch {
2478
+ throw new CommerceError(
2479
+ "CONFIG_INVALID",
2480
+ `Resource "${id}" has backend header "${name}" with an illegal name or value (a name must be an HTTP token; a value must not contain NUL, a character above U+00FF, or a line break except at either end, which fetch trims)`,
2481
+ { details: { path: `resources.${id}.backend.headers.${name}`, resourceId: id } }
2482
+ );
2483
+ }
2484
+ }
2485
+ }
2523
2486
  function validateAddress(path2, value) {
2524
2487
  if (!ADDRESS_PATTERN.test(value)) {
2525
2488
  throw new CommerceError(
@@ -2564,9 +2527,8 @@ async function loadConfig(options = {}) {
2564
2527
  `Configuration file "${configPath}" is not valid YAML: ${reason}`,
2565
2528
  {
2566
2529
  details: { path: configPath, ...details },
2567
- // Retained for programmatic callers. Nothing in the CLI prints a
2568
- // `cause`, and nothing should start: this is the object whose
2569
- // `.message` carries the source excerpt (see below).
2530
+ // Kept for programmatic callers. Never print it: its `.message` quotes
2531
+ // the source line (see describeYamlError).
2570
2532
  cause: error
2571
2533
  }
2572
2534
  );
@@ -2604,95 +2566,47 @@ function resolveConfigPath(explicitPath, env, cwd) {
2604
2566
  if (fromEnv) return path.resolve(cwd, fromEnv);
2605
2567
  return path.resolve(cwd, DEFAULT_CONFIG_FILENAME);
2606
2568
  }
2607
- function createDefaultIdGenerator() {
2608
- return {
2609
- next(prefix) {
2610
- const id = randomUUID();
2611
- return prefix ? `${prefix}_${id}` : id;
2612
- }
2569
+ var BEARER_PREFIX = /^Bearer +/i;
2570
+ function createBearerCheck(expectedToken) {
2571
+ const expected = sha256(expectedToken);
2572
+ return (header2) => {
2573
+ if (header2 === void 0 || !BEARER_PREFIX.test(header2)) return false;
2574
+ const presented = header2.replace(BEARER_PREFIX, "");
2575
+ return presented.length > 0 && timingSafeEqual(sha256(presented), expected);
2613
2576
  };
2614
2577
  }
2615
- var PINO_PRETTY_PATH = (() => {
2578
+ function sha256(value) {
2579
+ return createHash("sha256").update(value, "utf8").digest();
2580
+ }
2581
+ async function readCappedBody(req, maxBytes) {
2582
+ const chunks = [];
2583
+ let total = 0;
2616
2584
  try {
2617
- return createRequire(import.meta.url).resolve("pino-pretty");
2585
+ for await (const chunk of req) {
2586
+ const buffer = chunk;
2587
+ total += buffer.length;
2588
+ if (total > maxBytes) return { kind: "too-large" };
2589
+ chunks.push(buffer);
2590
+ }
2618
2591
  } catch {
2619
- return void 0;
2592
+ return { kind: "unreadable" };
2620
2593
  }
2621
- })();
2622
- var SECRET_FIELD_NAMES = [
2623
- "privateKey",
2624
- "signerPrivateKey",
2625
- "signature",
2626
- "seed",
2627
- "mnemonic",
2628
- "secret",
2629
- "challengeSecret",
2630
- "apiKey",
2631
- "adminToken",
2632
- "token"
2633
- ];
2634
- var SECRET_HEADERS = ["authorization", PAYMENT_HEADER, AUTHORIZATION_HEADER];
2635
- var REDACT_PATHS = [
2636
- ...SECRET_HEADERS.map((name) => `req.headers[${JSON.stringify(name)}]`),
2637
- ...SECRET_FIELD_NAMES,
2638
- ...SECRET_FIELD_NAMES.map((name) => `*.${name}`)
2639
- ];
2640
- function baseLoggerOptions(options) {
2641
- const nodeEnv = options.nodeEnv ?? process.env["NODE_ENV"] ?? "development";
2642
- const wantPretty = options.prettyPrint ?? (nodeEnv !== "production" && nodeEnv !== "test");
2643
- const usePretty = wantPretty && PINO_PRETTY_PATH !== void 0;
2644
- return {
2645
- level: options.level ?? process.env["LOG_LEVEL"] ?? (nodeEnv === "test" ? "silent" : "info"),
2646
- ...options.name !== void 0 ? { name: options.name } : {},
2647
- redact: { paths: [...REDACT_PATHS], censor: "[REDACTED]" },
2648
- ...usePretty ? { transport: { target: PINO_PRETTY_PATH } } : {}
2649
- };
2650
- }
2651
- function sanitizeUrl(url) {
2652
- if (url === void 0) return void 0;
2653
- const queryIndex = url.indexOf("?");
2654
- return queryIndex === -1 ? url : `${url.slice(0, queryIndex)}?[REDACTED]`;
2655
- }
2656
- function redactedReqSerializer(req) {
2657
- return {
2658
- method: req.method,
2659
- url: sanitizeUrl(req.url),
2660
- host: req.host,
2661
- remoteAddress: req.ip,
2662
- remotePort: req.socket?.remotePort
2663
- };
2664
- }
2665
- function fastifyLoggerOptions(options = {}) {
2666
- return {
2667
- ...baseLoggerOptions(options),
2668
- serializers: { req: redactedReqSerializer }
2669
- };
2670
- }
2671
- function buildNotFoundHandler() {
2672
- return (request, reply) => {
2673
- const safeUrl = sanitizeUrl(request.raw.url) ?? "";
2674
- const message = `Route ${request.method}:${safeUrl} not found`;
2675
- request.log.info(message);
2676
- reply.code(404).send({ message, error: "Not Found", statusCode: 404 });
2677
- };
2678
- }
2679
- function createGatewayLogger(options = {}) {
2680
- const instance = pino(baseLoggerOptions(options));
2681
- return { pino: instance, core: wrapPino(instance) };
2594
+ return { kind: "ok", text: Buffer.concat(chunks).toString("utf8") };
2682
2595
  }
2683
- function wrapPino(instance) {
2684
- const logger = {
2685
- debug: (obj, msg) => instance.debug(obj, msg),
2686
- info: (obj, msg) => instance.info(obj, msg),
2687
- warn: (obj, msg) => instance.warn(obj, msg),
2688
- error: (obj, msg) => instance.error(obj, msg),
2689
- child: (bindings) => wrapPino(instance.child(bindings))
2690
- };
2691
- return logger;
2596
+ function joinUrl(baseUrl, mountPath) {
2597
+ return `${baseUrl.replace(/\/+$/, "")}/${mountPath.replace(/^\/+|\/+$/g, "")}`;
2692
2598
  }
2693
- var CORS_METHODS = "GET,POST,DELETE,OPTIONS";
2694
- var CORS_HEADERS = "content-type,payment-signature,x-request-id,authorization";
2695
- var CORS_EXPOSED_HEADERS = "payment-required,payment-response,www-authenticate,payment-receipt,x-request-id";
2599
+
2600
+ // src/gateway/access-control.ts
2601
+ var CORS_METHODS = "GET,POST,OPTIONS";
2602
+ var CORS_HEADERS = [
2603
+ "content-type",
2604
+ PAYMENT_HEADER,
2605
+ AUTHORIZATION_HEADER,
2606
+ "x-request-id",
2607
+ "authorization"
2608
+ ].join(",");
2609
+ var CORS_EXPOSED_HEADERS = "payment-required,payment-response,www-authenticate,payment-receipt";
2696
2610
  function buildAccessControlHook(options) {
2697
2611
  const allowedOrigins = new Set(options.allowedOrigins.map((origin) => origin.toLowerCase()));
2698
2612
  const publicHostname = hostnameOf(options.publicBaseUrl);
@@ -2720,12 +2634,13 @@ function buildAccessControlHook(options) {
2720
2634
  };
2721
2635
  }
2722
2636
  function buildOperatorTokenHook(adminToken) {
2637
+ const isValidAdminToken = adminToken ? createBearerCheck(adminToken) : void 0;
2723
2638
  return async (request, reply) => {
2724
- if (!adminToken) {
2639
+ if (isValidAdminToken === void 0) {
2725
2640
  reply.status(404).send();
2726
2641
  return;
2727
2642
  }
2728
- if (!isValidAdminToken(request.headers.authorization, adminToken)) {
2643
+ if (!isValidAdminToken(request.headers.authorization)) {
2729
2644
  reply.status(401).send({ status: "error", code: "UNAUTHORIZED", message: "Admin token required" });
2730
2645
  }
2731
2646
  };
@@ -2746,14 +2661,6 @@ function isAllowedHost(hostHeader, publicHostname) {
2746
2661
  const hostname = hostnameOf(hostHeader);
2747
2662
  return hostname !== "" && (hostname === publicHostname || LOOPBACK_HOSTNAMES.has(hostname));
2748
2663
  }
2749
- function isValidAdminToken(authHeader, adminToken) {
2750
- const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice("Bearer ".length) : void 0;
2751
- if (provided === void 0) return false;
2752
- return timingSafeEqual(hashToken(provided), hashToken(adminToken));
2753
- }
2754
- function hashToken(value) {
2755
- return createHash("sha256").update(value, "utf8").digest();
2756
- }
2757
2664
 
2758
2665
  // src/gateway/adapters.ts
2759
2666
  var MOUNT_BODY_LIMIT_BYTES = 256 * 1024;
@@ -2766,7 +2673,7 @@ async function startAndMountAdapters(options) {
2766
2673
  try {
2767
2674
  await adapter.start(adapterContext);
2768
2675
  } catch (error) {
2769
- const detail = describeError3(error);
2676
+ const detail = errorMessage(error);
2770
2677
  adapterLogger.error(
2771
2678
  { err: detail },
2772
2679
  "Protocol adapter failed to start; gateway continues without it"
@@ -2860,7 +2767,7 @@ function mountHttpAdapter(server, adapter, logger) {
2860
2767
  try {
2861
2768
  await handleHttp(request.raw, reply.raw);
2862
2769
  } catch (error) {
2863
- logger.error({ err: describeError3(error) }, "Protocol adapter request handler threw");
2770
+ logger.error({ err: errorMessage(error) }, "Protocol adapter request handler threw");
2864
2771
  if (!reply.raw.headersSent) {
2865
2772
  reply.raw.statusCode = 500;
2866
2773
  reply.raw.end();
@@ -2927,7 +2834,7 @@ async function getAdapterHealth(runtime, clock) {
2927
2834
  try {
2928
2835
  return await runtime.adapter.health();
2929
2836
  } catch (error) {
2930
- return { status: "fail", detail: describeError3(error), checkedAt: clock.nowIso() };
2837
+ return { status: "fail", detail: errorMessage(error), checkedAt: clock.nowIso() };
2931
2838
  }
2932
2839
  }
2933
2840
  async function stopAdapters(runtimes, logger) {
@@ -2935,13 +2842,99 @@ async function stopAdapters(runtimes, logger) {
2935
2842
  try {
2936
2843
  await runtime.adapter.stop();
2937
2844
  } catch (error) {
2938
- logger.child({ adapter: runtime.adapter.name }).error({ err: describeError3(error) }, "Protocol adapter failed to stop cleanly");
2845
+ logger.child({ adapter: runtime.adapter.name }).error({ err: errorMessage(error) }, "Protocol adapter failed to stop cleanly");
2939
2846
  }
2940
2847
  }
2941
2848
  }
2942
- function describeError3(error) {
2849
+ function errorMessage(error) {
2943
2850
  return error instanceof Error ? error.message : String(error);
2944
2851
  }
2852
+ function createDefaultIdGenerator() {
2853
+ return {
2854
+ next(prefix) {
2855
+ const id = randomUUID();
2856
+ return prefix ? `${prefix}_${id}` : id;
2857
+ }
2858
+ };
2859
+ }
2860
+ var PINO_PRETTY_PATH = (() => {
2861
+ try {
2862
+ return createRequire(import.meta.url).resolve("pino-pretty");
2863
+ } catch {
2864
+ return void 0;
2865
+ }
2866
+ })();
2867
+ var SECRET_FIELD_NAMES = [
2868
+ "privateKey",
2869
+ "signerPrivateKey",
2870
+ "signature",
2871
+ "seed",
2872
+ "mnemonic",
2873
+ "secret",
2874
+ "challengeSecret",
2875
+ "apiKey",
2876
+ "adminToken",
2877
+ "token"
2878
+ ];
2879
+ var SECRET_HEADERS = ["authorization", PAYMENT_HEADER, AUTHORIZATION_HEADER];
2880
+ var REDACT_PATHS = [
2881
+ ...SECRET_HEADERS.map((name) => `req.headers[${JSON.stringify(name)}]`),
2882
+ ...SECRET_FIELD_NAMES,
2883
+ ...SECRET_FIELD_NAMES.map((name) => `*.${name}`)
2884
+ ];
2885
+ function baseLoggerOptions(options) {
2886
+ const nodeEnv = options.nodeEnv ?? process.env["NODE_ENV"] ?? "development";
2887
+ const wantPretty = options.prettyPrint ?? (nodeEnv !== "production" && nodeEnv !== "test");
2888
+ const usePretty = wantPretty && PINO_PRETTY_PATH !== void 0;
2889
+ return {
2890
+ level: options.level ?? process.env["LOG_LEVEL"] ?? (nodeEnv === "test" ? "silent" : "info"),
2891
+ ...options.name !== void 0 ? { name: options.name } : {},
2892
+ redact: { paths: [...REDACT_PATHS], censor: "[REDACTED]" },
2893
+ ...usePretty ? { transport: { target: PINO_PRETTY_PATH } } : {}
2894
+ };
2895
+ }
2896
+ function sanitizeUrl(url) {
2897
+ if (url === void 0) return void 0;
2898
+ const queryIndex = url.indexOf("?");
2899
+ return queryIndex === -1 ? url : `${url.slice(0, queryIndex)}?[REDACTED]`;
2900
+ }
2901
+ function redactedReqSerializer(req) {
2902
+ return {
2903
+ method: req.method,
2904
+ url: sanitizeUrl(req.url),
2905
+ host: req.host,
2906
+ remoteAddress: req.ip,
2907
+ remotePort: req.socket?.remotePort
2908
+ };
2909
+ }
2910
+ function fastifyLoggerOptions(options = {}) {
2911
+ return {
2912
+ ...baseLoggerOptions(options),
2913
+ serializers: { req: redactedReqSerializer }
2914
+ };
2915
+ }
2916
+ function buildNotFoundHandler() {
2917
+ return (request, reply) => {
2918
+ const safeUrl = sanitizeUrl(request.raw.url) ?? "";
2919
+ const message = `Route ${request.method}:${safeUrl} not found`;
2920
+ request.log.info(message);
2921
+ reply.code(404).send({ message, error: "Not Found", statusCode: 404 });
2922
+ };
2923
+ }
2924
+ function createGatewayLogger(options = {}) {
2925
+ const instance = pino(baseLoggerOptions(options));
2926
+ return { pino: instance, core: wrapPino(instance) };
2927
+ }
2928
+ function wrapPino(instance) {
2929
+ const logger = {
2930
+ debug: (obj, msg) => instance.debug(obj, msg),
2931
+ info: (obj, msg) => instance.info(obj, msg),
2932
+ warn: (obj, msg) => instance.warn(obj, msg),
2933
+ error: (obj, msg) => instance.error(obj, msg),
2934
+ child: (bindings) => wrapPino(instance.child(bindings))
2935
+ };
2936
+ return logger;
2937
+ }
2945
2938
 
2946
2939
  // src/gateway/public-resource.ts
2947
2940
  function toPublicResource(resource) {
@@ -2962,7 +2955,8 @@ function toPublicResource(resource) {
2962
2955
  var STORE_DETAIL = {
2963
2956
  pass: void 0,
2964
2957
  warn: "store-degraded",
2965
- fail: "store-unwritable"
2958
+ fail: "store-unwritable",
2959
+ threw: "store-unreachable"
2966
2960
  };
2967
2961
  var ADAPTER_DETAIL = {
2968
2962
  pass: void 0,
@@ -2979,108 +2973,102 @@ var AUTHORIZATION_PROVIDER_DETAIL = {
2979
2973
  warn: "authorization-provider-degraded",
2980
2974
  fail: "authorization-provider-unreachable"
2981
2975
  };
2982
- async function probeProvider(provider, kind, details, options) {
2976
+ async function probe(target, kind, details, options) {
2983
2977
  let health;
2978
+ let threw = false;
2984
2979
  try {
2985
- health = await provider.health();
2980
+ health = await target.health();
2986
2981
  } catch (error) {
2987
2982
  const message = error instanceof Error ? error.message : String(error);
2988
- options.logger.error({ err: message, provider: provider.name }, `${kind} health() threw`);
2983
+ options.logger.error({ err: message, name: target.name }, `${kind} health() threw`);
2989
2984
  health = { status: "fail", checkedAt: options.clock.nowIso() };
2985
+ threw = true;
2990
2986
  }
2991
2987
  if (health.detail !== void 0) {
2992
2988
  options.logger.debug(
2993
- { provider: provider.name, detail: health.detail },
2989
+ { name: target.name, detail: health.detail },
2994
2990
  `${kind} health detail (not sent to the client)`
2995
2991
  );
2996
2992
  }
2997
- const detail = details[health.status];
2993
+ const detail = threw ? details.threw ?? details.fail : details[health.status];
2998
2994
  return {
2999
- name: provider.name,
3000
- status: health.status,
3001
- ...detail !== void 0 ? { detail } : {}
2995
+ check: {
2996
+ name: target.name,
2997
+ status: health.status,
2998
+ ...detail !== void 0 ? { detail } : {}
2999
+ },
3000
+ health
3002
3001
  };
3003
3002
  }
3004
- async function checkReadiness(options) {
3005
- let storeHealth;
3006
- let storeThrew = false;
3007
- try {
3008
- storeHealth = await options.store.health();
3009
- } catch (error) {
3010
- const message = error instanceof Error ? error.message : String(error);
3011
- options.logger.error({ err: message }, "store.health() threw");
3012
- storeHealth = { status: "fail", checkedAt: options.clock.nowIso() };
3013
- storeThrew = true;
3014
- }
3015
- if (storeHealth.detail !== void 0) {
3016
- options.logger.debug(
3017
- { detail: storeHealth.detail },
3018
- "store health detail (not sent to the client)"
3019
- );
3020
- }
3021
- const adapterChecks = await Promise.all(
3022
- options.adapterRuntimes.map(async (runtime) => {
3023
- const health = await getAdapterHealth(runtime, options.clock);
3024
- if (health.detail !== void 0) {
3025
- options.logger.debug(
3026
- { adapter: runtime.adapter.name, detail: health.detail },
3027
- "adapter health detail (not sent to the client)"
3028
- );
3029
- }
3030
- const detail = ADAPTER_DETAIL[health.status];
3031
- return {
3032
- name: runtime.adapter.name,
3033
- status: health.status,
3034
- ...detail !== void 0 ? { detail } : {}
3035
- };
3036
- })
3037
- );
3038
- const paymentProviderChecks = await Promise.all(
3039
- options.paymentProviders.map(
3040
- (provider) => probeProvider(provider, "payment provider", PAYMENT_PROVIDER_DETAIL, options)
3041
- )
3042
- );
3043
- const authorizationProviderChecks = await Promise.all(
3044
- options.authorizationProviders.map(
3045
- (provider) => probeProvider(provider, "authorization provider", AUTHORIZATION_PROVIDER_DETAIL, options)
3003
+ async function evaluate(options) {
3004
+ const [store, adapters, paymentProviders, authorizationProviders] = await Promise.all([
3005
+ probe({ name: "store", health: () => options.store.health() }, "store", STORE_DETAIL, options),
3006
+ Promise.all(
3007
+ options.adapterRuntimes.map(async (runtime) => ({
3008
+ runtime,
3009
+ probed: await probe(
3010
+ // getAdapterHealth turns a throw into a failed result, so an adapter
3011
+ // failure is logged at debug as a detail rather than at error
3012
+ { name: runtime.adapter.name, health: () => getAdapterHealth(runtime, options.clock) },
3013
+ "adapter",
3014
+ ADAPTER_DETAIL,
3015
+ options
3016
+ )
3017
+ }))
3018
+ ),
3019
+ Promise.all(
3020
+ options.paymentProviders.map(
3021
+ (provider) => probe(provider, "payment provider", PAYMENT_PROVIDER_DETAIL, options)
3022
+ )
3023
+ ),
3024
+ // An unusable authorization provider blocks readiness on the same threshold
3025
+ // as a payment one: a resource that requires a mandate cannot be served
3026
+ // without it, and serving the challenge anyway promises what we cannot honor
3027
+ Promise.all(
3028
+ options.authorizationProviders.map(
3029
+ (provider) => probe(provider, "authorization provider", AUTHORIZATION_PROVIDER_DETAIL, options)
3030
+ )
3046
3031
  )
3047
- );
3048
- const storeReady = storeHealth.status !== "fail";
3049
- const adaptersReady = adapterChecks.every((check) => check.status !== "fail");
3050
- const paymentProvidersReady = paymentProviderChecks.every((check) => check.status !== "fail");
3051
- const authorizationProvidersReady = authorizationProviderChecks.every(
3052
- (check) => check.status !== "fail"
3053
- );
3054
- const storeDetail = storeThrew ? "store-unreachable" : STORE_DETAIL[storeHealth.status];
3032
+ ]);
3033
+ const checks = [
3034
+ store,
3035
+ ...adapters.map((a) => a.probed),
3036
+ ...paymentProviders,
3037
+ ...authorizationProviders
3038
+ ];
3055
3039
  return {
3056
- ready: storeReady && adaptersReady && paymentProvidersReady && authorizationProvidersReady,
3057
- store: {
3058
- name: "store",
3059
- status: storeHealth.status,
3060
- ...storeDetail !== void 0 ? { detail: storeDetail } : {}
3040
+ result: {
3041
+ ready: checks.every((probed) => probed.check.status !== "fail"),
3042
+ store: store.check,
3043
+ adapters: adapters.map((a) => a.probed.check),
3044
+ paymentProviders: paymentProviders.map((p) => p.check),
3045
+ authorizationProviders: authorizationProviders.map((p) => p.check)
3061
3046
  },
3062
- adapters: adapterChecks,
3063
- paymentProviders: paymentProviderChecks,
3064
- authorizationProviders: authorizationProviderChecks
3047
+ adapters: adapters.map(({ runtime, probed }) => ({
3048
+ descriptor: runtime.adapter.descriptor,
3049
+ health: probed.health
3050
+ }))
3065
3051
  };
3066
3052
  }
3067
3053
  var READINESS_TTL_MS = 2e3;
3068
3054
  function createReadinessProbe(options, ttlMs = READINESS_TTL_MS) {
3069
3055
  let cached;
3070
3056
  let inFlight;
3057
+ const current = async () => {
3058
+ const now = options.clock.monotonicMs();
3059
+ if (cached && now - cached.at < ttlMs) return cached.evaluation;
3060
+ if (inFlight) return inFlight;
3061
+ inFlight = evaluate(options).then((evaluation) => {
3062
+ cached = { evaluation, at: options.clock.monotonicMs() };
3063
+ return evaluation;
3064
+ }).finally(() => {
3065
+ inFlight = void 0;
3066
+ });
3067
+ return inFlight;
3068
+ };
3071
3069
  return {
3072
- async check() {
3073
- const now = options.clock.monotonicMs();
3074
- if (cached && now - cached.at < ttlMs) return cached.result;
3075
- if (inFlight) return inFlight;
3076
- inFlight = checkReadiness(options).then((result) => {
3077
- cached = { result, at: options.clock.monotonicMs() };
3078
- return result;
3079
- }).finally(() => {
3080
- inFlight = void 0;
3081
- });
3082
- return inFlight;
3083
- }
3070
+ check: async () => (await current()).result,
3071
+ adapterHealth: async () => (await current()).adapters
3084
3072
  };
3085
3073
  }
3086
3074
 
@@ -3098,13 +3086,11 @@ function publicProtocols(protocols) {
3098
3086
  acp: { enabled: protocols.acp.enabled, mountPath: protocols.acp.mountPath }
3099
3087
  };
3100
3088
  }
3101
- async function buildWellKnownDocument(options) {
3102
- const adapters = await Promise.all(
3103
- options.adapterRuntimes.map(async (runtime) => {
3104
- const health = await getAdapterHealth(runtime, options.clock);
3105
- return { ...runtime.adapter.descriptor, health: publicHealth(health) };
3106
- })
3107
- );
3089
+ function buildWellKnownDocument(options) {
3090
+ const adapters = options.adapters.map(({ descriptor, health }) => ({
3091
+ ...descriptor,
3092
+ health: publicHealth(health)
3093
+ }));
3108
3094
  const { x402, mpp } = options.config.payments;
3109
3095
  return {
3110
3096
  gateway: {
@@ -3128,14 +3114,11 @@ async function buildWellKnownDocument(options) {
3128
3114
  assetDecimals: x402.assetDecimals,
3129
3115
  payTo: x402.payTo,
3130
3116
  maxTimeoutSeconds: x402.maxTimeoutSeconds,
3131
- // Never the signerPrivateKey, and never the facilitator URL:
3132
- // a facilitator endpoint is a per-deployment operational detail
3133
- // that can carry a tenant path or an API key, exactly like
3134
- // `rpcUrl` above. Only whether it is local or remote is public.
3117
+ // Only local or remote is public: the facilitator URL can carry
3118
+ // a tenant path or an API key, and the signer key is secret
3135
3119
  facilitator: { mode: x402.facilitator.mode },
3136
- // Chain id 84532 is shared between the local dev chain and
3137
- // public Base Sepolia, so the network id alone cannot say which
3138
- // one a client is talking to. This can.
3120
+ // Chain id 84532 is both the local dev chain and public Base
3121
+ // Sepolia, so the network id alone cannot say which one this is
3139
3122
  mode: resolveDeploymentMode(
3140
3123
  requireNetworkProfile(x402.network, "payments.x402.network"),
3141
3124
  x402.facilitator.mode
@@ -3163,8 +3146,6 @@ async function buildWellKnownDocument(options) {
3163
3146
  }
3164
3147
 
3165
3148
  // src/gateway/routes.ts
3166
- var SSE_HEARTBEAT_MS = 15e3;
3167
- var MAX_SSE_SUBSCRIBERS = 32;
3168
3149
  function registerRoutes(options) {
3169
3150
  const { server } = options;
3170
3151
  const readinessProbe = createReadinessProbe({
@@ -3188,8 +3169,7 @@ function registerRoutes(options) {
3188
3169
  paymentProviders: options.paymentProviders,
3189
3170
  authorizationProviders: options.authorizationProviders,
3190
3171
  store: options.store,
3191
- adapterRuntimes: options.adapterRuntimes,
3192
- clock: options.clock
3172
+ adapters: await readinessProbe.adapterHealth()
3193
3173
  })
3194
3174
  );
3195
3175
  server.get("/api/resources", async () => ({
@@ -3198,81 +3178,35 @@ function registerRoutes(options) {
3198
3178
  server.post("/api/resources/:id/invoke", async (request, reply) => {
3199
3179
  await handleInvoke(request, reply, options);
3200
3180
  });
3201
- const receiptsTokenHook = buildOperatorTokenHook(options.config.server.adminToken);
3202
- const eventsTokenHook = buildOperatorTokenHook(options.config.server.adminToken);
3203
- const eventsStreamTokenHook = buildOperatorTokenHook(options.config.server.adminToken);
3204
- server.get("/api/receipts", { onRequest: receiptsTokenHook }, async (request, reply) => {
3205
- try {
3206
- const limit = parseLimit(request.query["limit"]);
3207
- return { receipts: await options.store.listReceipts(limit !== void 0 ? { limit } : {}) };
3208
- } catch (error) {
3209
- const commerceError = toCommerceError(error);
3210
- reply.status(commerceError.httpStatus).send(toErrorEnvelope(commerceError));
3211
- return void 0;
3212
- }
3213
- });
3214
- server.get("/api/events", { onRequest: eventsTokenHook }, async (request, reply) => {
3181
+ const tokenHook = buildOperatorTokenHook(options.config.server.adminToken);
3182
+ server.get(
3183
+ "/api/receipts",
3184
+ { onRequest: tokenHook },
3185
+ ledgerHandler("receipts", (list) => options.store.listReceipts(list))
3186
+ );
3187
+ server.get(
3188
+ "/api/events",
3189
+ { onRequest: tokenHook },
3190
+ ledgerHandler("events", (list) => options.store.listEvents(list))
3191
+ );
3192
+ }
3193
+ function ledgerHandler(key, list) {
3194
+ return async (request, reply) => {
3215
3195
  try {
3216
3196
  const limit = parseLimit(request.query["limit"]);
3217
- return { events: await options.store.listEvents(limit !== void 0 ? { limit } : {}) };
3197
+ return { [key]: await list(limit !== void 0 ? { limit } : {}) };
3218
3198
  } catch (error) {
3219
3199
  const commerceError = toCommerceError(error);
3220
3200
  reply.status(commerceError.httpStatus).send(toErrorEnvelope(commerceError));
3221
3201
  return void 0;
3222
3202
  }
3223
- });
3224
- let activeSseSubscribers = 0;
3225
- server.get("/api/events/stream", { onRequest: eventsStreamTokenHook }, (request, reply) => {
3226
- if (activeSseSubscribers >= MAX_SSE_SUBSCRIBERS) {
3227
- reply.status(503).send({
3228
- status: "error",
3229
- code: "UNAVAILABLE",
3230
- message: "Too many event-stream subscribers"
3231
- });
3232
- return;
3233
- }
3234
- reply.raw.writeHead(200, {
3235
- "Content-Type": "text/event-stream",
3236
- "Cache-Control": "no-cache",
3237
- Connection: "keep-alive"
3238
- });
3239
- activeSseSubscribers += 1;
3240
- reply.raw.write(": connected\n\n");
3241
- let writable = true;
3242
- reply.raw.on("drain", () => {
3243
- writable = true;
3244
- });
3245
- const unsubscribe = options.eventBus.subscribe((event) => {
3246
- queueMicrotask(() => {
3247
- if (!writable) return;
3248
- writable = reply.raw.write(`data: ${JSON.stringify(event)}
3249
-
3250
- `);
3251
- });
3252
- });
3253
- const heartbeat = setInterval(() => {
3254
- if (!writable) return;
3255
- writable = reply.raw.write(": heartbeat\n\n");
3256
- }, SSE_HEARTBEAT_MS);
3257
- heartbeat.unref();
3258
- let cleanedUp = false;
3259
- const cleanup = () => {
3260
- if (cleanedUp) return;
3261
- cleanedUp = true;
3262
- clearInterval(heartbeat);
3263
- unsubscribe();
3264
- activeSseSubscribers -= 1;
3265
- };
3266
- request.raw.on("close", cleanup);
3267
- reply.raw.on("error", cleanup);
3268
- reply.hijack();
3269
- });
3203
+ };
3270
3204
  }
3271
3205
  async function handleInvoke(request, reply, options) {
3272
3206
  const resourceId = request.params.id;
3273
3207
  try {
3274
3208
  const resource = options.resources.get(resourceId);
3275
- if (!resource || !resource.exposedVia.includes("http")) {
3209
+ if (!resource?.exposedVia.includes("http")) {
3276
3210
  throw new CommerceError("RESOURCE_NOT_FOUND", `Resource "${resourceId}" was not found`, {
3277
3211
  requestId: request.id,
3278
3212
  resourceId
@@ -3342,9 +3276,8 @@ function paymentProof(request, method) {
3342
3276
  }
3343
3277
  function errorPaymentSummary(error) {
3344
3278
  const payment = error.details?.["payment"];
3345
- if (typeof payment !== "object" || payment === null) return void 0;
3346
- const rec = payment;
3347
- const { status, provider, amount, currency, network, externalReference, receipt } = rec;
3279
+ if (!isRecord(payment)) return void 0;
3280
+ const { status, provider, amount, currency, network, externalReference, receipt } = payment;
3348
3281
  if (typeof status !== "string" || typeof provider !== "string" || typeof amount !== "string" || typeof currency !== "string") {
3349
3282
  return void 0;
3350
3283
  }
@@ -3397,6 +3330,11 @@ function parseLimit(value) {
3397
3330
  // src/gateway/server.ts
3398
3331
  var REQUEST_ID_HEADER = "x-request-id";
3399
3332
  var CLIENT_REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]{1,64}$/;
3333
+ function clientRequestIdOf(raw) {
3334
+ const header2 = raw.headers[REQUEST_ID_HEADER];
3335
+ const value = Array.isArray(header2) ? header2[0] : header2;
3336
+ return value !== void 0 && CLIENT_REQUEST_ID_PATTERN.test(value) ? value : void 0;
3337
+ }
3400
3338
  function withProviderBackedMethodsFirst(resource, providers) {
3401
3339
  const backed = (method) => providers.some((provider) => provider.name === method);
3402
3340
  const ordered = [
@@ -3416,26 +3354,28 @@ async function createGateway(options) {
3416
3354
  (resource) => withProviderBackedMethodsFirst(resource, options.paymentProviders)
3417
3355
  )
3418
3356
  );
3419
- const eventBus = createEventBus({ store: options.store, logger });
3357
+ const events = createStoreEventSink({ store: options.store, logger });
3420
3358
  const pipeline = createExecutionPipeline({
3421
3359
  resources,
3422
3360
  paymentProviders: options.paymentProviders,
3423
3361
  authorizationProviders,
3424
3362
  store: options.store,
3425
3363
  backend,
3426
- events: eventBus,
3364
+ events,
3427
3365
  logger,
3428
3366
  clock,
3429
3367
  ids
3430
3368
  });
3431
3369
  const server = Fastify({
3432
- // One number for both surfaces — see adapters.ts's doc comment
3433
- // on MOUNT_BODY_LIMIT_BYTES for why they're enforced two different ways.
3370
+ // Same cap as adapter mounts, which enforce it on the socket instead
3434
3371
  bodyLimit: MOUNT_BODY_LIMIT_BYTES,
3435
- genReqId: (rawReq) => {
3436
- const header2 = rawReq.headers[REQUEST_ID_HEADER];
3437
- const value = Array.isArray(header2) ? header2[0] : header2;
3438
- return value !== void 0 && CLIENT_REQUEST_ID_PATTERN.test(value) ? value : ids.next("req");
3372
+ genReqId: () => ids.next("req"),
3373
+ childLoggerFactory: (parent, bindings, childOptions, rawReq) => {
3374
+ const clientRequestId = clientRequestIdOf(rawReq);
3375
+ return parent.child(
3376
+ clientRequestId !== void 0 ? { ...bindings, clientRequestId } : bindings,
3377
+ childOptions
3378
+ );
3439
3379
  },
3440
3380
  logger: fastifyLoggerOptions({ name: `${options.config.merchant.id}-http` })
3441
3381
  });
@@ -3456,7 +3396,6 @@ async function createGateway(options) {
3456
3396
  store: options.store,
3457
3397
  paymentProviders: options.paymentProviders,
3458
3398
  authorizationProviders,
3459
- eventBus,
3460
3399
  clock,
3461
3400
  adapterRuntimes,
3462
3401
  logger
@@ -3464,7 +3403,7 @@ async function createGateway(options) {
3464
3403
  const context = {
3465
3404
  pipeline,
3466
3405
  resources,
3467
- events: eventBus,
3406
+ events,
3468
3407
  logger,
3469
3408
  clock,
3470
3409
  ids,
@@ -3497,6 +3436,554 @@ async function createGateway(options) {
3497
3436
  };
3498
3437
  }
3499
3438
 
3439
+ // src/protocols/a2a/constants.ts
3440
+ var A2A_SPEC_VERSION = "1.0.0";
3441
+ var A2A_PROTOCOL_VERSION = "1.0";
3442
+ var A2A_PROTOCOL_BINDING = "JSONRPC";
3443
+ var A2A_AGENT_CARD_PATH = "/.well-known/agent-card.json";
3444
+ var A2A_DEFAULT_MOUNT_PATH = "/a2a";
3445
+ var A2A_JSON_MEDIA_TYPE = "application/json";
3446
+ var A2A_DEFAULT_AGENT_NAME = "agent-commerce";
3447
+ var A2A_VERSION_HEADER = "a2a-version";
3448
+ var A2A_METHOD_SEND_MESSAGE = "SendMessage";
3449
+ var A2A_UNSUPPORTED_METHODS = [
3450
+ "SendStreamingMessage",
3451
+ "GetTask",
3452
+ "ListTasks",
3453
+ "CancelTask",
3454
+ "SubscribeToTask",
3455
+ "CreateTaskPushNotificationConfig",
3456
+ "GetTaskPushNotificationConfig",
3457
+ "ListTaskPushNotificationConfigs",
3458
+ "DeleteTaskPushNotificationConfig",
3459
+ "GetExtendedAgentCard"
3460
+ ];
3461
+ var A2A_TASK_STATE_COMPLETED = "TASK_STATE_COMPLETED";
3462
+ var A2A_TASK_STATE_FAILED = "TASK_STATE_FAILED";
3463
+
3464
+ // src/protocols/a2a/agent-card.ts
3465
+ function skillTags(resource) {
3466
+ return ["agent-commerce", resource.pricing.type === "free" ? "free" : "paid"];
3467
+ }
3468
+ function skillDescription(resource) {
3469
+ const base = resource.description ?? resource.name;
3470
+ if (resource.pricing.type === "fixed") {
3471
+ return `${base} Costs ${resource.pricing.amount} ${resource.pricing.currency} per call.`;
3472
+ }
3473
+ if (resource.pricing.type === "dynamic") {
3474
+ return `${base} Requires payment (amount determined at request time).`;
3475
+ }
3476
+ return base;
3477
+ }
3478
+ function buildAgentSkill(resource) {
3479
+ return {
3480
+ id: resource.id,
3481
+ name: resource.name,
3482
+ description: skillDescription(resource),
3483
+ tags: skillTags(resource),
3484
+ inputModes: [A2A_JSON_MEDIA_TYPE],
3485
+ outputModes: [A2A_JSON_MEDIA_TYPE]
3486
+ };
3487
+ }
3488
+ function buildAgentCard(options) {
3489
+ return {
3490
+ name: options.name,
3491
+ description: options.description,
3492
+ version: options.version,
3493
+ supportedInterfaces: [
3494
+ {
3495
+ url: joinUrl(options.publicBaseUrl, options.mountPath),
3496
+ protocolBinding: A2A_PROTOCOL_BINDING,
3497
+ protocolVersion: A2A_PROTOCOL_VERSION
3498
+ }
3499
+ ],
3500
+ capabilities: { streaming: false, pushNotifications: false, extendedAgentCard: false },
3501
+ defaultInputModes: [A2A_JSON_MEDIA_TYPE],
3502
+ defaultOutputModes: [A2A_JSON_MEDIA_TYPE],
3503
+ skills: options.resources.map(buildAgentSkill)
3504
+ };
3505
+ }
3506
+
3507
+ // src/protocols/a2a/descriptor.ts
3508
+ var A2A_CAPABILITIES = ["agent-card", "jsonrpc", "SendMessage"];
3509
+ var A2A_UNSUPPORTED = [
3510
+ // Methods, as the protocol names them; the transport refuses the same list
3511
+ ...A2A_UNSUPPORTED_METHODS,
3512
+ // Transports other than the one binding served
3513
+ "HTTP+JSON/REST binding",
3514
+ "gRPC binding",
3515
+ // Behaviors
3516
+ "SSE",
3517
+ "long-running task persistence",
3518
+ "task resumption",
3519
+ "push notifications",
3520
+ "multi-turn conversational continuation",
3521
+ "authenticated extended agent cards",
3522
+ "A2A authentication schemes",
3523
+ "artifact types beyond Agent Commerce outcome data"
3524
+ ];
3525
+ function buildDescriptor(implementationVersion) {
3526
+ return {
3527
+ name: "a2a",
3528
+ kind: "protocol",
3529
+ implementationVersion,
3530
+ supportedSpec: A2A_SPEC_VERSION,
3531
+ capabilities: A2A_CAPABILITIES,
3532
+ status: "experimental",
3533
+ unsupported: A2A_UNSUPPORTED
3534
+ };
3535
+ }
3536
+
3537
+ // src/protocols/a2a/jsonrpc.ts
3538
+ var JSONRPC_PARSE_ERROR = -32700;
3539
+ var JSONRPC_INVALID_REQUEST = -32600;
3540
+ var JSONRPC_METHOD_NOT_FOUND = -32601;
3541
+ var JSONRPC_INVALID_PARAMS = -32602;
3542
+ var JSONRPC_INTERNAL_ERROR = -32603;
3543
+ var A2A_ERROR_UNSUPPORTED_OPERATION = -32004;
3544
+ function readId(value) {
3545
+ if (typeof value === "string" || typeof value === "number") return value;
3546
+ return null;
3547
+ }
3548
+ function jsonRpcResult(id, result) {
3549
+ return { jsonrpc: "2.0", id, result };
3550
+ }
3551
+ function jsonRpcError(id, code, message) {
3552
+ return { jsonrpc: "2.0", id, error: { code, message } };
3553
+ }
3554
+ function parseJsonRpcRequest(rawBody) {
3555
+ let payload;
3556
+ try {
3557
+ payload = JSON.parse(rawBody);
3558
+ } catch {
3559
+ return { ok: false, id: null, error: { code: JSONRPC_PARSE_ERROR, message: "Invalid JSON." } };
3560
+ }
3561
+ if (Array.isArray(payload)) {
3562
+ return {
3563
+ ok: false,
3564
+ id: null,
3565
+ error: {
3566
+ code: JSONRPC_INVALID_REQUEST,
3567
+ message: "Batch requests are not supported: send a single JSON-RPC request object."
3568
+ }
3569
+ };
3570
+ }
3571
+ if (!isRecord(payload)) {
3572
+ return {
3573
+ ok: false,
3574
+ id: null,
3575
+ error: { code: JSONRPC_INVALID_REQUEST, message: "Request must be a JSON-RPC 2.0 object." }
3576
+ };
3577
+ }
3578
+ const id = readId(payload["id"]);
3579
+ if (payload["jsonrpc"] !== "2.0") {
3580
+ return {
3581
+ ok: false,
3582
+ id,
3583
+ error: { code: JSONRPC_INVALID_REQUEST, message: 'Request must set "jsonrpc" to "2.0".' }
3584
+ };
3585
+ }
3586
+ const method = payload["method"];
3587
+ if (typeof method !== "string" || method.length === 0) {
3588
+ return {
3589
+ ok: false,
3590
+ id,
3591
+ error: { code: JSONRPC_INVALID_REQUEST, message: 'Request must carry a "method" string.' }
3592
+ };
3593
+ }
3594
+ const params = payload["params"];
3595
+ if (params !== void 0 && !isRecord(params)) {
3596
+ return {
3597
+ ok: false,
3598
+ id,
3599
+ error: {
3600
+ code: JSONRPC_INVALID_PARAMS,
3601
+ message: 'Request "params" must be an object.'
3602
+ }
3603
+ };
3604
+ }
3605
+ return { ok: true, request: { id, method, params: params ?? {} } };
3606
+ }
3607
+ var A2A_USER_ROLE = "ROLE_USER";
3608
+ var UnknownRecord = z.record(z.string(), z.unknown());
3609
+ var MessageSchema = z.object({
3610
+ role: z.string(),
3611
+ messageId: z.string().optional(),
3612
+ parts: z.array(UnknownRecord),
3613
+ taskId: z.string().optional(),
3614
+ contextId: z.string().optional(),
3615
+ referenceTaskIds: z.array(z.string()).optional()
3616
+ });
3617
+ var ParamsSchema = z.object({
3618
+ message: MessageSchema,
3619
+ taskId: z.string().optional(),
3620
+ contextId: z.string().optional()
3621
+ });
3622
+ function invalid(message) {
3623
+ return new CommerceError("INPUT_INVALID", message);
3624
+ }
3625
+ function unsupported(message) {
3626
+ return new CommerceError("PROTOCOL_UNSUPPORTED", message);
3627
+ }
3628
+ function assertNoContinuation(params) {
3629
+ const message = params.message;
3630
+ if (params.taskId !== void 0 || message.taskId !== void 0) {
3631
+ throw unsupported("Task continuation is not supported: send a request with no taskId.");
3632
+ }
3633
+ if (params.contextId !== void 0 || message.contextId !== void 0) {
3634
+ throw unsupported(
3635
+ "Multi-turn conversational continuation is not supported: send a request with no contextId."
3636
+ );
3637
+ }
3638
+ if (message.referenceTaskIds !== void 0 && message.referenceTaskIds.length > 0) {
3639
+ throw unsupported("Referencing previous tasks is not supported.");
3640
+ }
3641
+ }
3642
+ function assertSupportedPart(part) {
3643
+ if ("file" in part || "raw" in part || "url" in part) {
3644
+ throw unsupported("File and URL parts are not supported: send a structured data part.");
3645
+ }
3646
+ if ("text" in part) {
3647
+ throw unsupported("Text parts are not supported: send a structured data part.");
3648
+ }
3649
+ if (!("data" in part)) {
3650
+ throw invalid('Message part carries no "data": send a structured data part.');
3651
+ }
3652
+ const mediaType = part["mediaType"];
3653
+ if (mediaType !== void 0 && mediaType !== A2A_JSON_MEDIA_TYPE) {
3654
+ throw unsupported(
3655
+ `Media type "${String(mediaType)}" is not supported: parts must be ${A2A_JSON_MEDIA_TYPE}.`
3656
+ );
3657
+ }
3658
+ }
3659
+ function parseInvocation(rawParams) {
3660
+ const parsed = ParamsSchema.safeParse(rawParams);
3661
+ if (!parsed.success) {
3662
+ throw invalid(
3663
+ 'Request must carry a "message" with a "role" and a "parts" array of structured data parts.'
3664
+ );
3665
+ }
3666
+ const params = parsed.data;
3667
+ const message = params.message;
3668
+ assertNoContinuation(params);
3669
+ if (message.role !== A2A_USER_ROLE) {
3670
+ throw invalid(`Unsupported message role "${message.role}": only ${A2A_USER_ROLE} is accepted.`);
3671
+ }
3672
+ if (message.parts.length === 0) {
3673
+ throw invalid("Message carries no parts: send exactly one structured data part.");
3674
+ }
3675
+ if (message.parts.length > 1) {
3676
+ throw unsupported(
3677
+ `Multi-part messages are not supported: send exactly one structured data part (received ${message.parts.length}).`
3678
+ );
3679
+ }
3680
+ const part = message.parts[0];
3681
+ if (part === void 0) throw invalid("Message carries no parts.");
3682
+ assertSupportedPart(part);
3683
+ const data = part["data"];
3684
+ if (!isRecord(data)) {
3685
+ throw invalid('Message part "data" must be a JSON object.');
3686
+ }
3687
+ const resourceId = data["resource"];
3688
+ if (typeof resourceId !== "string") {
3689
+ throw invalid('Message part data must carry a "resource" string naming a canonical resource.');
3690
+ }
3691
+ if (resourceId.length === 0) {
3692
+ throw invalid('Message part data "resource" must not be empty.');
3693
+ }
3694
+ const rawInput = data["input"];
3695
+ if (rawInput !== void 0 && !isRecord(rawInput)) {
3696
+ throw invalid('Message part data "input" must be a JSON object.');
3697
+ }
3698
+ return {
3699
+ resourceId,
3700
+ input: rawInput ?? {},
3701
+ ...message.messageId !== void 0 ? { messageId: message.messageId } : {}
3702
+ };
3703
+ }
3704
+
3705
+ // src/protocols/a2a/task-mapping.ts
3706
+ function dataPayload(body) {
3707
+ return isRecord(body) ? body : { value: body ?? null };
3708
+ }
3709
+ function task(identity, state, artifact) {
3710
+ return {
3711
+ id: identity.taskId,
3712
+ contextId: identity.contextId,
3713
+ status: { state, timestamp: identity.timestamp },
3714
+ artifacts: [{ artifactId: identity.artifactId, ...artifact }]
3715
+ };
3716
+ }
3717
+ function completedTask(outcome, identity) {
3718
+ return task(identity, A2A_TASK_STATE_COMPLETED, {
3719
+ name: outcome.resourceId,
3720
+ parts: [{ data: dataPayload(outcome.body), mediaType: A2A_JSON_MEDIA_TYPE }],
3721
+ // Same meta key MCP attaches its summary under, so a buyer reads the
3722
+ // record of their own purchase the same way on either protocol
3723
+ metadata: { [DELIVERY_SUMMARY_META_KEY]: { ...toDeliverySummary(outcome) } }
3724
+ });
3725
+ }
3726
+ function paymentRequiredTask(outcome, identity) {
3727
+ return task(identity, A2A_TASK_STATE_FAILED, {
3728
+ name: outcome.resourceId,
3729
+ parts: [{ data: { ...toPaymentRequiredEnvelope(outcome) }, mediaType: A2A_JSON_MEDIA_TYPE }]
3730
+ });
3731
+ }
3732
+ function failedTask(error, identity) {
3733
+ return task(identity, A2A_TASK_STATE_FAILED, {
3734
+ parts: [{ data: { ...toErrorEnvelope(error) }, mediaType: A2A_JSON_MEDIA_TYPE }]
3735
+ });
3736
+ }
3737
+
3738
+ // src/protocols/a2a/adapter.ts
3739
+ var MAX_REQUEST_BODY_BYTES = 256 * 1024;
3740
+ var DEFAULT_AGENT_DESCRIPTION = "Agent Commerce Gateway: canonical commerce resources exposed as A2A skills.";
3741
+ var A2aProtocolAdapter = class {
3742
+ name = "a2a";
3743
+ mountPath;
3744
+ descriptor;
3745
+ additionalHttpRoutes;
3746
+ agentName;
3747
+ agentDescription;
3748
+ agentVersion;
3749
+ context;
3750
+ started = false;
3751
+ // The card lists only a2a-exposed resources, but a caller can name any id,
3752
+ // so the adapter refuses another protocol's resource itself
3753
+ skillsById = /* @__PURE__ */ new Map();
3754
+ // Built once at start: resources are fixed at config load, and a per-request
3755
+ // build would be work an unauthenticated GET could trigger at will
3756
+ card;
3757
+ constructor(options = {}) {
3758
+ this.mountPath = options.mountPath ?? A2A_DEFAULT_MOUNT_PATH;
3759
+ this.agentName = options.agentName ?? A2A_DEFAULT_AGENT_NAME;
3760
+ this.agentDescription = options.agentDescription ?? DEFAULT_AGENT_DESCRIPTION;
3761
+ this.agentVersion = options.agentVersion ?? PACKAGE_VERSION;
3762
+ this.descriptor = buildDescriptor(PACKAGE_VERSION);
3763
+ this.additionalHttpRoutes = [
3764
+ {
3765
+ method: "GET",
3766
+ path: A2A_AGENT_CARD_PATH,
3767
+ handleHttp: (req, res) => this.handleAgentCard(req, res)
3768
+ }
3769
+ ];
3770
+ }
3771
+ async start(context) {
3772
+ this.context = context;
3773
+ this.started = false;
3774
+ let resources = [];
3775
+ try {
3776
+ resources = context.resources.listExposedVia("a2a");
3777
+ } catch (err) {
3778
+ context.logger.error(
3779
+ { err: toLogInfo(err) },
3780
+ "a2a adapter: failed to list a2a-exposed resources"
3781
+ );
3782
+ resources = [];
3783
+ }
3784
+ this.skillsById = new Map(resources.map((resource) => [resource.id, resource]));
3785
+ this.card = buildAgentCard({
3786
+ name: this.agentName,
3787
+ description: this.agentDescription,
3788
+ version: this.agentVersion,
3789
+ publicBaseUrl: context.publicBaseUrl,
3790
+ mountPath: this.mountPath,
3791
+ resources
3792
+ });
3793
+ this.started = true;
3794
+ context.logger.info(
3795
+ { mountPath: this.mountPath, cardPath: A2A_AGENT_CARD_PATH, skillCount: resources.length },
3796
+ "a2a adapter started"
3797
+ );
3798
+ }
3799
+ /** `GET /.well-known/agent-card.json` */
3800
+ async handleAgentCard(req, res) {
3801
+ try {
3802
+ if (!this.started || this.card === void 0) {
3803
+ this.writeJson(res, 503, { error: "A2A adapter is not running." });
3804
+ return;
3805
+ }
3806
+ if (req.method !== "GET") {
3807
+ this.writeJson(res, 405, { error: "Method not allowed. The Agent Card is read-only." });
3808
+ return;
3809
+ }
3810
+ this.writeJson(res, 200, this.card);
3811
+ } catch (err) {
3812
+ this.context?.logger.error({ err: toLogInfo(err) }, "a2a adapter: agent card request failed");
3813
+ this.writeJson(res, 500, { error: "Internal server error." });
3814
+ }
3815
+ }
3816
+ /** `POST <mountPath>`: the A2A JSON-RPC endpoint */
3817
+ async handleHttp(req, res) {
3818
+ try {
3819
+ if (!this.started) {
3820
+ this.writeJson(
3821
+ res,
3822
+ 503,
3823
+ jsonRpcError(null, JSONRPC_INTERNAL_ERROR, "A2A adapter is not running.")
3824
+ );
3825
+ return;
3826
+ }
3827
+ if (req.method !== "POST") {
3828
+ this.writeJson(
3829
+ res,
3830
+ 405,
3831
+ jsonRpcError(
3832
+ null,
3833
+ JSONRPC_INVALID_REQUEST,
3834
+ "Method not allowed. This endpoint only accepts POST."
3835
+ )
3836
+ );
3837
+ return;
3838
+ }
3839
+ const version = req.headers[A2A_VERSION_HEADER];
3840
+ const declared = Array.isArray(version) ? version[0] : version;
3841
+ if (declared !== A2A_PROTOCOL_VERSION) {
3842
+ this.writeJson(
3843
+ res,
3844
+ 200,
3845
+ jsonRpcError(
3846
+ null,
3847
+ A2A_ERROR_UNSUPPORTED_OPERATION,
3848
+ `Unsupported A2A protocol version. Send the ${A2A_VERSION_HEADER} header with "${A2A_PROTOCOL_VERSION}".`
3849
+ )
3850
+ );
3851
+ return;
3852
+ }
3853
+ const read = await readCappedBody(req, MAX_REQUEST_BODY_BYTES);
3854
+ if (read.kind !== "ok") {
3855
+ this.writeJson(
3856
+ res,
3857
+ 200,
3858
+ jsonRpcError(null, JSONRPC_PARSE_ERROR, "Could not read the request body.")
3859
+ );
3860
+ return;
3861
+ }
3862
+ const parsed = parseJsonRpcRequest(read.text);
3863
+ if (!parsed.ok) {
3864
+ this.writeJson(res, 200, jsonRpcError(parsed.id, parsed.error.code, parsed.error.message));
3865
+ return;
3866
+ }
3867
+ this.writeJson(
3868
+ res,
3869
+ 200,
3870
+ await this.dispatch(parsed.request.id, parsed.request.method, parsed.request.params)
3871
+ );
3872
+ } catch (err) {
3873
+ this.context?.logger.error({ err: toLogInfo(err) }, "a2a adapter: request handling failed");
3874
+ this.writeJson(
3875
+ res,
3876
+ 200,
3877
+ jsonRpcError(null, JSONRPC_INTERNAL_ERROR, "Internal server error.")
3878
+ );
3879
+ }
3880
+ }
3881
+ async dispatch(id, method, params) {
3882
+ if (A2A_UNSUPPORTED_METHODS.includes(method)) {
3883
+ return jsonRpcError(
3884
+ id,
3885
+ A2A_ERROR_UNSUPPORTED_OPERATION,
3886
+ `A2A method "${method}" is not supported by this deployment.`
3887
+ );
3888
+ }
3889
+ if (method !== A2A_METHOD_SEND_MESSAGE) {
3890
+ return jsonRpcError(id, JSONRPC_METHOD_NOT_FOUND, `Unknown method "${method}".`);
3891
+ }
3892
+ let invocation;
3893
+ try {
3894
+ invocation = parseInvocation(params);
3895
+ } catch (err) {
3896
+ const error = toCommerceError(err);
3897
+ const code = error.code === "PROTOCOL_UNSUPPORTED" ? A2A_ERROR_UNSUPPORTED_OPERATION : JSONRPC_INVALID_PARAMS;
3898
+ return jsonRpcError(id, code, error.message);
3899
+ }
3900
+ return this.execute(id, invocation);
3901
+ }
3902
+ // One accepted invocation, one `pipeline.execute()`
3903
+ async execute(id, invocation) {
3904
+ const context = this.context;
3905
+ if (context === void 0) {
3906
+ return jsonRpcError(id, JSONRPC_INTERNAL_ERROR, "A2A adapter is not running.");
3907
+ }
3908
+ const resource = this.skillsById.get(invocation.resourceId);
3909
+ if (resource === void 0) {
3910
+ return this.taskResult(
3911
+ id,
3912
+ failedTask(
3913
+ new CommerceError(
3914
+ "RESOURCE_NOT_FOUND",
3915
+ `Unknown canonical resource "${invocation.resourceId}".`
3916
+ ),
3917
+ this.taskIdentity(context, context.ids.next("a2a"))
3918
+ )
3919
+ );
3920
+ }
3921
+ const requestId = context.ids.next("a2a");
3922
+ const identity = this.taskIdentity(context, requestId);
3923
+ try {
3924
+ const { input, payment, authorization } = extractReservedInputFields(
3925
+ invocation.input,
3926
+ resource,
3927
+ requestId
3928
+ );
3929
+ const request = {
3930
+ requestId,
3931
+ resourceId: invocation.resourceId,
3932
+ input,
3933
+ protocol: "a2a",
3934
+ receivedAt: context.clock.nowIso(),
3935
+ ...payment !== void 0 ? { payment } : {},
3936
+ ...authorization !== void 0 ? { authorization } : {}
3937
+ };
3938
+ const outcome = await context.pipeline.execute(request);
3939
+ return this.taskResult(
3940
+ id,
3941
+ outcome.kind === "payment-required" ? paymentRequiredTask(outcome, identity) : completedTask(outcome, identity)
3942
+ );
3943
+ } catch (err) {
3944
+ const error = toCommerceError(err);
3945
+ context.logger.warn(
3946
+ { resourceId: invocation.resourceId, requestId, err: toLogInfo(error) },
3947
+ "a2a adapter: execution failed"
3948
+ );
3949
+ return this.taskResult(id, failedTask(error, identity));
3950
+ }
3951
+ }
3952
+ taskIdentity(context, requestId) {
3953
+ return {
3954
+ taskId: requestId,
3955
+ contextId: context.ids.next("a2a-ctx"),
3956
+ artifactId: context.ids.next("a2a-artifact"),
3957
+ timestamp: context.clock.nowIso()
3958
+ };
3959
+ }
3960
+ // A2A's JSON-RPC result wraps the terminal task
3961
+ taskResult(id, task2) {
3962
+ return jsonRpcResult(id, { task: task2 });
3963
+ }
3964
+ async health() {
3965
+ const checkedAt = this.context?.clock.nowIso() ?? (/* @__PURE__ */ new Date()).toISOString();
3966
+ if (!this.started || this.card === void 0) {
3967
+ return { status: "fail", detail: "A2A adapter has not been started.", checkedAt };
3968
+ }
3969
+ return { status: "pass", detail: `${this.skillsById.size} skill(s) published.`, checkedAt };
3970
+ }
3971
+ async stop() {
3972
+ this.started = false;
3973
+ this.card = void 0;
3974
+ this.skillsById = /* @__PURE__ */ new Map();
3975
+ this.context = void 0;
3976
+ }
3977
+ writeJson(res, status, body) {
3978
+ if (res.headersSent) return;
3979
+ res.writeHead(status, { "content-type": A2A_JSON_MEDIA_TYPE });
3980
+ res.end(JSON.stringify(body));
3981
+ }
3982
+ };
3983
+ function createA2aAdapter(options = {}) {
3984
+ return new A2aProtocolAdapter(options);
3985
+ }
3986
+
3500
3987
  // src/protocols/acp/checkout-mapping.ts
3501
3988
  function toCanonicalRequest(options) {
3502
3989
  const { request, resourceId, requestId, receivedAt, idempotencyKey } = options;
@@ -3511,13 +3998,14 @@ function toCanonicalRequest(options) {
3511
3998
  }
3512
3999
  function canonicalInput(request) {
3513
4000
  const keys = ACP_OPERATION_INPUT_KEYS[request.route.operation];
4001
+ const optionalKeys = ACP_OPERATION_OPTIONAL_INPUT_KEYS[request.route.operation] ?? [];
3514
4002
  const input = {};
3515
4003
  if (keys.includes("path") && request.route.sessionId !== void 0) {
3516
4004
  input["path"] = { checkout_session_id: request.route.sessionId };
3517
4005
  }
3518
4006
  if (keys.includes("body")) {
3519
4007
  input["body"] = request.body;
3520
- } else if (request.route.acceptsBody && Object.keys(request.body).length > 0) {
4008
+ } else if (optionalKeys.includes("body") && Object.keys(request.body).length > 0) {
3521
4009
  input["body"] = request.body;
3522
4010
  }
3523
4011
  return input;
@@ -3540,21 +4028,21 @@ var ACP_UNSUPPORTED = [
3540
4028
  "standalone orders service",
3541
4029
  "delegate_payment",
3542
4030
  "delegate_authentication",
3543
- // Bindings and delivery directions.
4031
+ // Bindings and delivery directions
3544
4032
  "ACP MCP transport binding",
3545
4033
  "webhooks",
3546
4034
  "outbound merchant-to-agent delivery",
3547
4035
  "ACP client role",
3548
- // Request authentication beyond the bearer token.
4036
+ // Request authentication beyond the bearer token
3549
4037
  "Signature header verification",
3550
4038
  "Timestamp replay-window verification",
3551
- // Extensions and versioning.
4039
+ // Extensions, versioning and payment handlers
3552
4040
  "discount extension",
3553
4041
  "general ACP extension framework",
3554
4042
  "multiple ACP API versions",
3555
4043
  "seller-backed payment handler integration"
3556
4044
  ];
3557
- function buildDescriptor(implementationVersion) {
4045
+ function buildDescriptor2(implementationVersion) {
3558
4046
  return {
3559
4047
  name: "acp",
3560
4048
  kind: "protocol",
@@ -3577,9 +4065,8 @@ function buildAcpDiscoveryDocument(options) {
3577
4065
  ...metadata.documentationUrl !== void 0 ? { documentation_url: metadata.documentationUrl } : {}
3578
4066
  },
3579
4067
  api_base_url: joinUrl(options.publicBaseUrl, options.mountPath),
3580
- // Only the REST binding. ACP's MCP transport is its own fixed checkout
3581
- // binding and is not the generic MCP adapter this gateway also serves;
3582
- // advertising it here because "we do MCP" would be a false claim.
4068
+ // REST only: ACP's MCP transport is a fixed checkout binding, not the
4069
+ // generic MCP adapter this gateway also serves
3583
4070
  transports: ["rest"],
3584
4071
  capabilities: {
3585
4072
  services: ["checkout"],
@@ -3589,9 +4076,6 @@ function buildAcpDiscoveryDocument(options) {
3589
4076
  }
3590
4077
  };
3591
4078
  }
3592
- function joinUrl(baseUrl, mountPath) {
3593
- return `${baseUrl.replace(/\/+$/, "")}/${mountPath.replace(/^\/+|\/+$/g, "")}`;
3594
- }
3595
4079
 
3596
4080
  // src/protocols/acp/errors.ts
3597
4081
  function acpFailure(status, type, code, message, extra = {}) {
@@ -3615,10 +4099,10 @@ function writeAcpFailure(res, failure, headers = {}) {
3615
4099
  writeAcpJson(res, failure.status, failure.error, headers);
3616
4100
  }
3617
4101
  function operationKey(scope) {
3618
- return sha256(`acp-operation:${scope.deployment}:${scope.endpoint}:${scope.key}`);
4102
+ return sha2562(`acp-operation:${scope.deployment}:${scope.endpoint}:${scope.key}`);
3619
4103
  }
3620
4104
  function requestFingerprint(body) {
3621
- return sha256(canonicalize(body));
4105
+ return sha2562(canonicalize(body));
3622
4106
  }
3623
4107
  function canonicalize(value) {
3624
4108
  if (value === null) return "null";
@@ -3629,7 +4113,7 @@ function canonicalize(value) {
3629
4113
  }
3630
4114
  return JSON.stringify(value) ?? "null";
3631
4115
  }
3632
- function sha256(value) {
4116
+ function sha2562(value) {
3633
4117
  return createHash("sha256").update(value, "utf8").digest("hex");
3634
4118
  }
3635
4119
 
@@ -3646,6 +4130,7 @@ function createAcpIdempotencyStore(options) {
3646
4130
  logger
3647
4131
  });
3648
4132
  migrate(db, logger);
4133
+ resolveOrphanedClaims(db, logger);
3649
4134
  const selectStmt = db.prepare(
3650
4135
  `SELECT state, fingerprint, status, body_json FROM acp_idempotency
3651
4136
  WHERE deployment = ? AND endpoint = ? AND idempotency_key = ?`
@@ -3727,6 +4212,15 @@ function createAcpIdempotencyStore(options) {
3727
4212
  }
3728
4213
  };
3729
4214
  }
4215
+ function resolveOrphanedClaims(db, logger) {
4216
+ const { changes } = db.prepare("UPDATE acp_idempotency SET state = 'unresolved' WHERE state = 'in_flight'").run();
4217
+ if (changes > 0) {
4218
+ logger.warn(
4219
+ { unresolved: changes },
4220
+ "acp idempotency: marked claims left in flight by an earlier process as unresolved; reconcile these operations against the merchant before trusting a retry"
4221
+ );
4222
+ }
4223
+ }
3730
4224
  function migrate(db, logger) {
3731
4225
  const current = db.pragma("user_version", { simple: true });
3732
4226
  if (current >= SCHEMA_VERSION) return;
@@ -3761,17 +4255,6 @@ function migrate(db, logger) {
3761
4255
  });
3762
4256
  apply();
3763
4257
  }
3764
- var BEARER_PREFIX = /^Bearer +/i;
3765
- function isAuthorizedBearer(header2, expectedToken) {
3766
- if (header2 === void 0) return false;
3767
- if (!BEARER_PREFIX.test(header2)) return false;
3768
- const presented = header2.replace(BEARER_PREFIX, "");
3769
- if (presented.length === 0) return false;
3770
- return timingSafeEqual(sha2562(presented), sha2562(expectedToken));
3771
- }
3772
- function sha2562(value) {
3773
- return createHash("sha256").update(value, "utf8").digest();
3774
- }
3775
4258
 
3776
4259
  // src/protocols/acp/router.ts
3777
4260
  var MAX_SESSION_ID_LENGTH = 128;
@@ -7777,12 +8260,11 @@ var validators;
7777
8260
  function acpValidators() {
7778
8261
  if (validators !== void 0) return validators;
7779
8262
  const ajv = new Ajv2020({
7780
- // The vendored document is the released spec: annotations we do not know
7781
- // are the spec's business, not a reason to refuse to start.
8263
+ // The vendored document is the released spec; unknown annotations in it
8264
+ // are no reason to refuse to start
7782
8265
  strict: false,
7783
- // One failure is all that leaves the adapter, and collecting every error
7784
- // in a deeply nested cart is work an unauthenticated caller could ask for
7785
- // repeatedly.
8266
+ // Only one failure leaves the adapter, and collecting every error in a
8267
+ // deeply nested cart is work a caller could request repeatedly
7786
8268
  allErrors: false
7787
8269
  });
7788
8270
  addFormats(ajv);
@@ -7864,7 +8346,7 @@ async function guardAcpRequest(req, options) {
7864
8346
  );
7865
8347
  }
7866
8348
  const route = matched.route;
7867
- if (!isAuthorizedBearer(header(req, "authorization"), options.token)) {
8349
+ if (!options.isAuthorized(header(req, "authorization"))) {
7868
8350
  return failed(
7869
8351
  acpFailure(
7870
8352
  401,
@@ -7922,7 +8404,7 @@ async function guardAcpRequest(req, options) {
7922
8404
  }
7923
8405
  idempotencyKey = presented;
7924
8406
  }
7925
- const read = await readBody(req, options.maxBodyBytes ?? ACP_MAX_REQUEST_BODY_BYTES);
8407
+ const read = await readCappedBody(req, options.maxBodyBytes ?? ACP_MAX_REQUEST_BODY_BYTES);
7926
8408
  if (read.kind === "too-large") {
7927
8409
  return failed(
7928
8410
  acpFailure(
@@ -8020,21 +8502,6 @@ function header(req, name) {
8020
8502
  const value = req.headers[name];
8021
8503
  return Array.isArray(value) ? value[0] : value;
8022
8504
  }
8023
- async function readBody(req, maxBytes) {
8024
- const chunks = [];
8025
- let total = 0;
8026
- try {
8027
- for await (const chunk of req) {
8028
- const buffer = chunk;
8029
- total += buffer.length;
8030
- if (total > maxBytes) return { kind: "too-large" };
8031
- chunks.push(buffer);
8032
- }
8033
- } catch {
8034
- return { kind: "unreadable" };
8035
- }
8036
- return { kind: "ok", text: Buffer.concat(chunks).toString("utf8") };
8037
- }
8038
8505
 
8039
8506
  // src/protocols/acp/response-mapping.ts
8040
8507
  var ACP_SUCCESS_STATUS = {
@@ -8192,15 +8659,12 @@ var AcpProtocolAdapter = class {
8192
8659
  mountPath;
8193
8660
  descriptor;
8194
8661
  additionalHttpRoutes;
8195
- token;
8662
+ isAuthorized;
8196
8663
  operations;
8197
8664
  idempotencyOptions;
8198
8665
  /**
8199
- * What an idempotency claim is scoped to, from `context.publicBaseUrl`.
8200
- *
8201
- * Not readonly because, unlike the token, it is known only once the adapter
8202
- * starts. Worth that: scoping by the credential meant a rotation freed
8203
- * every outstanding claim.
8666
+ * The deployment an idempotency claim is scoped to, `context.publicBaseUrl`.
8667
+ * Not readonly because it is known only once the adapter starts.
8204
8668
  */
8205
8669
  deployment;
8206
8670
  discoveryMetadata;
@@ -8208,17 +8672,16 @@ var AcpProtocolAdapter = class {
8208
8672
  resources = /* @__PURE__ */ new Map();
8209
8673
  context;
8210
8674
  started = false;
8211
- // Built once at start: the document is fixed by config, and rebuilding it
8212
- // per request would let an unauthenticated GET do work a caller controls the
8213
- // cost of.
8675
+ // Built once at start: the document is fixed by config, and a per-request
8676
+ // build would be work an unauthenticated GET could trigger at will
8214
8677
  discovery;
8215
8678
  constructor(options) {
8216
8679
  this.mountPath = options.mountPath;
8217
- this.token = options.token;
8680
+ this.isAuthorized = createBearerCheck(options.token);
8218
8681
  this.operations = options.operations;
8219
8682
  this.idempotencyOptions = options.idempotency;
8220
8683
  this.discoveryMetadata = options.discovery;
8221
- this.descriptor = buildDescriptor(PACKAGE_VERSION);
8684
+ this.descriptor = buildDescriptor2(PACKAGE_VERSION);
8222
8685
  this.additionalHttpRoutes = [
8223
8686
  {
8224
8687
  method: "GET",
@@ -8235,12 +8698,12 @@ var AcpProtocolAdapter = class {
8235
8698
  mountPath: this.mountPath,
8236
8699
  ...this.discoveryMetadata !== void 0 ? { metadata: this.discoveryMetadata } : {}
8237
8700
  });
8238
- const invalid = validateAcpDocument("discoveryResponse", document);
8239
- if (invalid !== void 0) {
8701
+ const invalid2 = validateAcpDocument("discoveryResponse", document);
8702
+ if (invalid2 !== void 0) {
8240
8703
  throw new CommerceError(
8241
8704
  "CONFIG_INVALID",
8242
- `ACP discovery document does not conform to the ${this.descriptor.supportedSpec} snapshot at "${invalid.path ?? "$"}" (${invalid.code}) - check protocols.acp.discovery`,
8243
- { details: { path: invalid.path ?? "$", code: invalid.code } }
8705
+ `ACP discovery document does not conform to the ${this.descriptor.supportedSpec} snapshot at "${invalid2.path ?? "$"}" (${invalid2.code}) - check protocols.acp.discovery`,
8706
+ { details: { path: invalid2.path ?? "$", code: invalid2.code } }
8244
8707
  );
8245
8708
  }
8246
8709
  const exposed = new Map(
@@ -8277,7 +8740,7 @@ var AcpProtocolAdapter = class {
8277
8740
  "acp adapter started"
8278
8741
  );
8279
8742
  }
8280
- /** `GET /.well-known/acp.json` - public, unauthenticated, cacheable. */
8743
+ /** `GET /.well-known/acp.json`: public, unauthenticated, cacheable */
8281
8744
  async handleDiscovery(req, res) {
8282
8745
  try {
8283
8746
  if (!this.started || this.discovery === void 0) {
@@ -8309,7 +8772,7 @@ var AcpProtocolAdapter = class {
8309
8772
  this.fail(res, err, "acp adapter: discovery request failed");
8310
8773
  }
8311
8774
  }
8312
- /** `<mountPath>/checkout_sessions...` - the five stable checkout routes. */
8775
+ /** `<mountPath>/checkout_sessions...`: the five stable checkout routes */
8313
8776
  async handleHttp(req, res) {
8314
8777
  try {
8315
8778
  if (!this.started) {
@@ -8326,7 +8789,7 @@ var AcpProtocolAdapter = class {
8326
8789
  }
8327
8790
  const guarded = await guardAcpRequest(req, {
8328
8791
  mountPath: this.mountPath,
8329
- token: this.token
8792
+ isAuthorized: this.isAuthorized
8330
8793
  });
8331
8794
  if (!guarded.ok) {
8332
8795
  writeAcpFailure(res, guarded);
@@ -8340,12 +8803,9 @@ var AcpProtocolAdapter = class {
8340
8803
  }
8341
8804
  }
8342
8805
  /**
8343
- * One request, at most one execution.
8344
- *
8345
- * A body-bearing route claims its idempotency key *before* the work starts,
8346
- * so a retry that arrives while the first one is still running finds the
8347
- * claim instead of starting a second checkout. A route with no key (the GET)
8348
- * is read-only and needs none.
8806
+ * One request, at most one execution. A body-bearing route claims its
8807
+ * idempotency key before the work starts, so a retry arriving meanwhile finds
8808
+ * the claim; the read-only GET carries no key.
8349
8809
  */
8350
8810
  async dispatch(request) {
8351
8811
  const store = this.idempotency;
@@ -8406,13 +8866,7 @@ var AcpProtocolAdapter = class {
8406
8866
  }
8407
8867
  return outcome.response;
8408
8868
  }
8409
- /**
8410
- * One accepted request, one `pipeline.execute()`.
8411
- *
8412
- * Nothing here prices a resource, inspects a payment proof or calls a
8413
- * merchant backend: the adapter builds a canonical request and reads back
8414
- * what the pipeline decided.
8415
- */
8869
+ // One accepted request, one `pipeline.execute()`
8416
8870
  async runCheckout(request, idempotencyKey) {
8417
8871
  const context = this.context;
8418
8872
  const resource = this.resources.get(request.route.operation);
@@ -8464,7 +8918,7 @@ var AcpProtocolAdapter = class {
8464
8918
  resourceId: resource.id,
8465
8919
  requestId: canonical.requestId,
8466
8920
  reached: reachedFor(error.code),
8467
- err: error.toInfo()
8921
+ err: toLogInfo(error)
8468
8922
  },
8469
8923
  "acp adapter: checkout execution failed"
8470
8924
  );
@@ -8474,10 +8928,7 @@ var AcpProtocolAdapter = class {
8474
8928
  };
8475
8929
  }
8476
8930
  }
8477
- /**
8478
- * Protocol-owned headers only. A merchant backend's own headers are never
8479
- * proxied onto an ACP response.
8480
- */
8931
+ // Protocol-owned headers only; a merchant backend's headers are never proxied
8481
8932
  responseHeaders(request, response) {
8482
8933
  const replayed = response?.replayed === true;
8483
8934
  return {
@@ -8488,7 +8939,7 @@ var AcpProtocolAdapter = class {
8488
8939
  };
8489
8940
  }
8490
8941
  fail(res, err, message) {
8491
- this.context?.logger.error({ err: toCommerceError(err).toInfo() }, message);
8942
+ this.context?.logger.error({ err: toLogInfo(err) }, message);
8492
8943
  writeAcpFailure(
8493
8944
  res,
8494
8945
  acpFailure(500, "processing_error", "internal_error", "Internal server error.")
@@ -8551,8 +9002,8 @@ function receiptToRow(receipt) {
8551
9002
  payment_json: receipt.payment !== void 0 ? JSON.stringify(redact(receipt.payment)) : null,
8552
9003
  delivered_at: receipt.deliveredAt,
8553
9004
  backend_status: receipt.backendStatus,
8554
- duration_ms: receipt.durationMs !== void 0 ? receipt.durationMs : null,
8555
- protocol: receipt.protocol !== void 0 ? receipt.protocol : null,
9005
+ duration_ms: receipt.durationMs ?? null,
9006
+ protocol: receipt.protocol ?? null,
8556
9007
  metadata_json: receipt.metadata !== void 0 ? JSON.stringify(redact(receipt.metadata)) : null,
8557
9008
  authorization_json: receipt.authorization !== void 0 ? JSON.stringify(redact(receipt.authorization)) : null
8558
9009
  };
@@ -8579,12 +9030,12 @@ function eventToRow(event) {
8579
9030
  id: event.id,
8580
9031
  type: event.type,
8581
9032
  request_id: event.requestId,
8582
- resource_id: event.resourceId !== void 0 ? event.resourceId : null,
9033
+ resource_id: event.resourceId ?? null,
8583
9034
  at: event.at,
8584
- adapter: event.adapter !== void 0 ? event.adapter : null,
8585
- payment_provider: event.paymentProvider !== void 0 ? event.paymentProvider : null,
8586
- duration_ms: event.durationMs !== void 0 ? event.durationMs : null,
8587
- status: event.status !== void 0 ? event.status : null,
9035
+ adapter: event.adapter ?? null,
9036
+ payment_provider: event.paymentProvider ?? null,
9037
+ duration_ms: event.durationMs ?? null,
9038
+ status: event.status ?? null,
8588
9039
  data_json: event.data !== void 0 ? JSON.stringify(redact(event.data)) : null
8589
9040
  };
8590
9041
  }
@@ -8688,7 +9139,7 @@ var MIGRATIONS = [
8688
9139
  }
8689
9140
  }
8690
9141
  ];
8691
- var SCHEMA_VERSION2 = MIGRATIONS[MIGRATIONS.length - 1]?.version ?? 0;
9142
+ var SCHEMA_VERSION2 = MIGRATIONS.at(-1)?.version ?? 0;
8692
9143
  function migrate2(db) {
8693
9144
  const current = db.pragma("user_version", { simple: true });
8694
9145
  for (const migration of MIGRATIONS) {
@@ -8751,10 +9202,8 @@ function createSqliteReceiptStore(options) {
8751
9202
  );
8752
9203
  const insertPaymentAttemptStmt = db.prepare(
8753
9204
  `INSERT INTO payment_attempts (id, request_id, resource_id, provider, replay_key, status, amount, currency, payer, payee, external_reference, rejection_reason, created_at, updated_at)
8754
- VALUES (@id, @request_id, @resource_id, @provider, @replay_key, @status, @amount, @currency, @payer, @payee, @external_reference, @rejection_reason, @created_at, @updated_at)`
8755
- );
8756
- const getPaymentAttemptByIdStmt = db.prepare(
8757
- "SELECT * FROM payment_attempts WHERE id = ?"
9205
+ VALUES (@id, @request_id, @resource_id, @provider, @replay_key, @status, @amount, @currency, @payer, @payee, @external_reference, @rejection_reason, @created_at, @updated_at)
9206
+ RETURNING *`
8758
9207
  );
8759
9208
  const updatePaymentAttemptStmt = db.prepare(
8760
9209
  `UPDATE payment_attempts SET status = @status,
@@ -8805,8 +9254,9 @@ function createSqliteReceiptStore(options) {
8805
9254
  async reservePaymentAttempt(reservation) {
8806
9255
  const now = clock.nowIso();
8807
9256
  const id = ids.next("attempt");
9257
+ let row;
8808
9258
  try {
8809
- insertPaymentAttemptStmt.run({
9259
+ row = insertPaymentAttemptStmt.get({
8810
9260
  id,
8811
9261
  request_id: reservation.requestId,
8812
9262
  resource_id: reservation.resourceId,
@@ -8826,19 +9276,12 @@ function createSqliteReceiptStore(options) {
8826
9276
  if (isUniqueConstraintOn(err, "payment_attempts.replay_key")) {
8827
9277
  throw new CommerceError(
8828
9278
  "PAYMENT_REPLAYED",
8829
- `Payment authorisation has already been used (replayKey=${reservation.replayKey})`,
9279
+ `Payment authorization has already been used (replayKey=${reservation.replayKey})`,
8830
9280
  { requestId: reservation.requestId, resourceId: reservation.resourceId }
8831
9281
  );
8832
9282
  }
8833
9283
  throw toCommerceError(err, "STORAGE_ERROR", "Failed to reserve payment attempt");
8834
9284
  }
8835
- const row = getPaymentAttemptByIdStmt.get(id);
8836
- if (row === void 0) {
8837
- throw new CommerceError(
8838
- "STORAGE_ERROR",
8839
- "Payment attempt vanished immediately after insert"
8840
- );
8841
- }
8842
9285
  return rowToPaymentAttempt(row);
8843
9286
  },
8844
9287
  async updatePaymentAttempt(update) {
@@ -8876,11 +9319,11 @@ function createSqliteReceiptStore(options) {
8876
9319
  const rows = listOptions.requestId !== void 0 ? listReceiptsByRequestStmt.all(listOptions.requestId, limit) : listReceiptsStmt.all(limit);
8877
9320
  return rows.map(rowToReceipt);
8878
9321
  },
8879
- /** Exact total, ignoring the listReceipts clamp — see the interface doc for why this exists. */
9322
+ // Exact total, not bounded by the listReceipts clamp (see ReceiptStore.countReceipts)
8880
9323
  async countReceipts() {
8881
9324
  return countReceiptsStmt.get()?.count ?? 0;
8882
9325
  },
8883
- /** Exact count of non-2xx receipts — see the interface doc for why this exists. */
9326
+ // Exact count of non-2xx receipts (see ReceiptStore.countUndeliveredReceipts)
8884
9327
  async countUndeliveredReceipts() {
8885
9328
  return countUndeliveredReceiptsStmt.get()?.count ?? 0;
8886
9329
  },
@@ -8959,6 +9402,6 @@ function createSqliteReceiptStore(options) {
8959
9402
  return store;
8960
9403
  }
8961
9404
 
8962
- export { ACP_API_VERSION, ACP_SPEC_VERSION, ACP_WELL_KNOWN_PATH, createAcpAdapter, createGateway, createSqliteReceiptStore, loadConfig, parseConfig, createSqliteReceiptStore as receipts };
9405
+ export { A2A_AGENT_CARD_PATH, A2A_PROTOCOL_VERSION, A2A_SPEC_VERSION, ACP_API_VERSION, ACP_SPEC_VERSION, ACP_WELL_KNOWN_PATH, createA2aAdapter, createAcpAdapter, createGateway, createSqliteReceiptStore, loadConfig, parseConfig, createSqliteReceiptStore as receipts };
8963
9406
  //# sourceMappingURL=index.js.map
8964
9407
  //# sourceMappingURL=index.js.map