@adcp/sdk 16.1.0 → 16.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/AGENTS.md +2 -0
  2. package/dist/lib/core/AgentClient.js +17 -1
  3. package/dist/lib/core/AgentClient.mjs +17 -1
  4. package/dist/lib/core/SingleAgentClient.js +13 -0
  5. package/dist/lib/core/SingleAgentClient.mjs +13 -0
  6. package/dist/lib/core/TaskExecutor.js +88 -13
  7. package/dist/lib/core/TaskExecutor.mjs +89 -14
  8. package/dist/lib/errors/index.d.mts +34 -0
  9. package/dist/lib/errors/index.d.ts +34 -0
  10. package/dist/lib/errors/index.js +11 -0
  11. package/dist/lib/errors/index.mjs +10 -0
  12. package/dist/lib/governance/authorization.d.mts +66 -0
  13. package/dist/lib/governance/authorization.d.ts +66 -0
  14. package/dist/lib/governance/authorization.js +181 -37
  15. package/dist/lib/governance/authorization.mjs +183 -36
  16. package/dist/lib/governance/index.d.mts +2 -2
  17. package/dist/lib/governance/index.d.ts +2 -2
  18. package/dist/lib/governance/index.js +4 -2
  19. package/dist/lib/governance/index.mjs +3 -1
  20. package/dist/lib/identity-3.3/index.d.mts +1 -0
  21. package/dist/lib/identity-3.3/index.d.ts +1 -0
  22. package/dist/lib/identity-3.3/index.js +3 -1
  23. package/dist/lib/identity-3.3/index.mjs +1 -0
  24. package/dist/lib/identity-3.3/jws-artifacts.d.mts +147 -0
  25. package/dist/lib/identity-3.3/jws-artifacts.d.ts +147 -0
  26. package/dist/lib/identity-3.3/jws-artifacts.js +258 -0
  27. package/dist/lib/identity-3.3/jws-artifacts.mjs +234 -0
  28. package/dist/lib/identity-3.3/revocation.d.mts +7 -0
  29. package/dist/lib/identity-3.3/revocation.d.ts +7 -0
  30. package/dist/lib/identity-3.3/revocation.js +13 -0
  31. package/dist/lib/identity-3.3/revocation.mjs +13 -0
  32. package/dist/lib/index.d.mts +3 -3
  33. package/dist/lib/index.d.ts +3 -3
  34. package/dist/lib/index.js +4 -0
  35. package/dist/lib/index.mjs +4 -0
  36. package/dist/lib/media-buy/compatibility.d.mts +1 -0
  37. package/dist/lib/media-buy/compatibility.d.ts +1 -0
  38. package/dist/lib/media-buy/compatibility.js +33 -10
  39. package/dist/lib/media-buy/compatibility.mjs +32 -14
  40. package/dist/lib/negotiation/buyer.js +11 -5
  41. package/dist/lib/negotiation/buyer.mjs +11 -5
  42. package/dist/lib/negotiation/types.d.mts +4 -2
  43. package/dist/lib/negotiation/types.d.ts +4 -2
  44. package/dist/lib/negotiation/verification.d.mts +15 -6
  45. package/dist/lib/negotiation/verification.d.ts +15 -6
  46. package/dist/lib/negotiation/verification.js +146 -27
  47. package/dist/lib/negotiation/verification.mjs +146 -28
  48. package/dist/lib/negotiation/version.d.mts +6 -0
  49. package/dist/lib/negotiation/version.d.ts +6 -0
  50. package/dist/lib/negotiation/version.js +60 -0
  51. package/dist/lib/negotiation/version.mjs +33 -0
  52. package/dist/lib/registry/types.generated.d.mts +24 -1
  53. package/dist/lib/registry/types.generated.d.ts +24 -1
  54. package/dist/lib/server/create-adcp-server.js +1 -1
  55. package/dist/lib/server/create-adcp-server.mjs +1 -1
  56. package/dist/lib/server/error-arm-tools.d.mts +10 -10
  57. package/dist/lib/server/error-arm-tools.d.ts +10 -10
  58. package/dist/lib/server/error-arm-tools.js +22 -58
  59. package/dist/lib/server/error-arm-tools.mjs +30 -65
  60. package/dist/lib/server/index.d.mts +2 -2
  61. package/dist/lib/server/index.d.ts +2 -2
  62. package/dist/lib/server/index.js +2 -0
  63. package/dist/lib/server/index.mjs +2 -0
  64. package/dist/lib/signing/web-bot-auth.js +5 -0
  65. package/dist/lib/signing/web-bot-auth.mjs +4 -0
  66. package/dist/lib/testing/compliance/comply.js +17 -0
  67. package/dist/lib/testing/compliance/comply.mjs +17 -0
  68. package/dist/lib/testing/index.d.mts +3 -0
  69. package/dist/lib/testing/index.d.ts +3 -0
  70. package/dist/lib/testing/index.js +44 -2
  71. package/dist/lib/testing/index.mjs +42 -0
  72. package/dist/lib/testing/storyboard/collection-selection.d.mts +259 -0
  73. package/dist/lib/testing/storyboard/collection-selection.d.ts +259 -0
  74. package/dist/lib/testing/storyboard/collection-selection.js +384 -0
  75. package/dist/lib/testing/storyboard/collection-selection.mjs +348 -0
  76. package/dist/lib/testing/storyboard/context.js +12 -6
  77. package/dist/lib/testing/storyboard/context.mjs +12 -6
  78. package/dist/lib/testing/storyboard/fixture-resolution.d.mts +39 -1
  79. package/dist/lib/testing/storyboard/fixture-resolution.d.ts +39 -1
  80. package/dist/lib/testing/storyboard/fixture-resolution.js +216 -12
  81. package/dist/lib/testing/storyboard/fixture-resolution.mjs +218 -12
  82. package/dist/lib/testing/storyboard/index.d.mts +9 -2
  83. package/dist/lib/testing/storyboard/index.d.ts +9 -2
  84. package/dist/lib/testing/storyboard/index.js +51 -2
  85. package/dist/lib/testing/storyboard/index.mjs +49 -1
  86. package/dist/lib/testing/storyboard/prepared-fixtures.d.mts +471 -0
  87. package/dist/lib/testing/storyboard/prepared-fixtures.d.ts +471 -0
  88. package/dist/lib/testing/storyboard/prepared-fixtures.js +434 -0
  89. package/dist/lib/testing/storyboard/prepared-fixtures.mjs +389 -0
  90. package/dist/lib/testing/storyboard/protected-context.d.mts +127 -0
  91. package/dist/lib/testing/storyboard/protected-context.d.ts +127 -0
  92. package/dist/lib/testing/storyboard/protected-context.js +301 -0
  93. package/dist/lib/testing/storyboard/protected-context.mjs +267 -0
  94. package/dist/lib/testing/storyboard/rejection-hints.js +13 -4
  95. package/dist/lib/testing/storyboard/rejection-hints.mjs +13 -4
  96. package/dist/lib/testing/storyboard/runner.d.mts +24 -0
  97. package/dist/lib/testing/storyboard/runner.d.ts +24 -0
  98. package/dist/lib/testing/storyboard/runner.js +1061 -105
  99. package/dist/lib/testing/storyboard/runner.mjs +1075 -106
  100. package/dist/lib/testing/storyboard/seeding.d.mts +15 -1
  101. package/dist/lib/testing/storyboard/seeding.d.ts +15 -1
  102. package/dist/lib/testing/storyboard/seeding.js +272 -12
  103. package/dist/lib/testing/storyboard/seeding.mjs +274 -13
  104. package/dist/lib/testing/storyboard/types.d.mts +207 -10
  105. package/dist/lib/testing/storyboard/types.d.ts +207 -10
  106. package/dist/lib/testing/storyboard/types.js +1 -1
  107. package/dist/lib/testing/storyboard/types.mjs +1 -1
  108. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  109. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  110. package/dist/lib/testing/storyboard/validations.js +95 -0
  111. package/dist/lib/testing/storyboard/validations.mjs +95 -0
  112. package/dist/lib/validation/bundled-schema-store.js +5 -0
  113. package/dist/lib/validation/bundled-schema-store.mjs +4 -0
  114. package/dist/lib/validation/schema-loader.js +19 -2
  115. package/dist/lib/validation/schema-loader.mjs +22 -3
  116. package/dist/lib/version.d.mts +3 -3
  117. package/dist/lib/version.d.ts +3 -3
  118. package/dist/lib/version.js +3 -3
  119. package/dist/lib/version.mjs +3 -3
  120. package/docs/TYPE-SUMMARY.md +2 -2
  121. package/docs/guides/COLLECTION-FIXTURE-FOUNDATION.md +850 -0
  122. package/docs/guides/PROPOSAL-TERMS-VERIFICATION.md +66 -0
  123. package/docs/llms.txt +4 -3
  124. package/docs/migration-14.x-rc-worksheet.md +4 -4
  125. package/docs/migration-agent-identity-3.3.md +51 -6
  126. package/package.json +6 -4
@@ -25,6 +25,7 @@ __export(verification_exports, {
25
25
  assertRefineProposalsResponseShape: () => assertRefineProposalsResponseShape,
26
26
  isStrictDateTime: () => isStrictDateTime,
27
27
  proposalTermsDigest: () => proposalTermsDigest,
28
+ selectRefinementSchemaVersion: () => selectRefinementSchemaVersion,
28
29
  validateRefineProposalsResponseShape: () => validateRefineProposalsResponseShape,
29
30
  verifyProposalCommercialTerms: () => verifyProposalCommercialTerms,
30
31
  verifyProposalTermsDigest: () => verifyProposalTermsDigest,
@@ -36,9 +37,10 @@ var import_json_depth = require('../utils/json-depth.js');
36
37
  var import_jcs = require('../utils/jcs.js');
37
38
  var import_schema_loader = require('../validation/schema-loader.js');
38
39
  var import_version = require('../version.js');
40
+ var import_errors = require('../errors/index.js');
41
+ var import_version2 = require('./version.js');
39
42
  var import_commercial_terms_schema = require('./commercial-terms-schema.js');
40
43
  var import_commercial_terms_keywords = require('./commercial-terms-keywords.js');
41
- const ADCP_32_RELEASE = /^3\.2(?:-[A-Za-z0-9]+(?:[.-][A-Za-z0-9]+)*)?$/;
42
44
  const MAX_COMMERCIAL_TERMS_BYTES = 256 * 1024;
43
45
  const MAX_COMMERCIAL_TERMS_NODES = 5e4;
44
46
  const MAX_COMMERCIAL_TERMS_MISMATCHES = 100;
@@ -296,7 +298,8 @@ function isStrictDateTime(value) {
296
298
  }
297
299
  function verifyRefineProposalsResponse(request, response, options = {}) {
298
300
  const issues = [];
299
- if (!validateResponseShape(response, issues)) return { ok: false, issues };
301
+ const adcpVersion = selectRefinementSchemaVersion(request.adcp_version, response, options, issues);
302
+ if (adcpVersion === void 0 || !validateResponseShape(response, issues, adcpVersion)) return { ok: false, issues };
300
303
  if (response.status === "submitted") {
301
304
  push(issues, "shape", "status", "semantic proposal verification requires a completed response");
302
305
  return { ok: false, issues };
@@ -338,30 +341,126 @@ function assertRefineProposalsResponse(request, response, options = {}) {
338
341
  const result = verifyRefineProposalsResponse(request, response, options);
339
342
  if (!result.ok) throw new ProposalResponseVerificationError(result.issues);
340
343
  }
341
- function validateRefineProposalsResponseShape(response) {
344
+ function validateRefineProposalsResponseShape(response, options = {}) {
342
345
  const issues = [];
343
- validateResponseShape(response, issues);
346
+ validateResponseShape(
347
+ response,
348
+ issues,
349
+ options.adcpVersion ?? import_version.ADCP_VERSION,
350
+ options.adcpVersion && (0, import_version.toReleasePrecisionVersion)(options.adcpVersion)
351
+ );
344
352
  return { ok: issues.length === 0, issues };
345
353
  }
346
- function assertRefineProposalsResponseShape(response) {
347
- const result = validateRefineProposalsResponseShape(response);
354
+ function assertRefineProposalsResponseShape(response, options = {}) {
355
+ const result = validateRefineProposalsResponseShape(response, options);
348
356
  if (!result.ok) throw new ProposalResponseVerificationError(result.issues);
349
357
  }
350
- function validateResponseShape(value, issues) {
358
+ function selectRefinementSchemaVersion(requested, response, options, issues) {
359
+ const configured = options.adcpVersion ?? import_version.ADCP_VERSION;
360
+ const configuredWire = (0, import_version.toReleasePrecisionVersion)(configured);
361
+ if (!(0, import_version2.isProposalRefinementVersion)(configuredWire)) {
362
+ shape(issues, "adcp_version", `selected schema ${(0, import_version2.describeRefinementVersion)(configured)} must be AdCP 3.2 or 3.3`);
363
+ return void 0;
364
+ }
365
+ const ceiling = requested ?? configuredWire;
366
+ if (!(0, import_version2.isProposalRefinementVersion)(ceiling)) {
367
+ shape(
368
+ issues,
369
+ "adcp_version",
370
+ `request ceiling must be release-precision AdCP 3.2 or 3.3; received ${(0, import_version2.describeRefinementVersion)(ceiling)}`
371
+ );
372
+ return void 0;
373
+ }
374
+ const echo = isRecord(response) ? response.adcp_version : void 0;
375
+ if (echo !== void 0 && !(0, import_version2.isProposalRefinementVersion)(echo)) {
376
+ shape(
377
+ issues,
378
+ "adcp_version",
379
+ `response must echo release-precision AdCP 3.2 or 3.3 at or below request ceiling ${ceiling}; received ${(0, import_version2.describeRefinementVersion)(echo)}`
380
+ );
381
+ return void 0;
382
+ }
383
+ const metadata = options.servedAdcpVersion === void 0 ? void 0 : (0, import_version.toReleasePrecisionVersion)(options.servedAdcpVersion);
384
+ if (metadata !== void 0 && !(0, import_version2.isProposalRefinementVersion)(metadata)) {
385
+ shape(
386
+ issues,
387
+ "adcp_version",
388
+ `served version metadata must identify AdCP 3.2 or 3.3; received ${(0, import_version2.describeRefinementVersion)(options.servedAdcpVersion)}`
389
+ );
390
+ return void 0;
391
+ }
392
+ if (metadata !== void 0 && echo !== void 0 && metadata !== echo) {
393
+ shape(issues, "adcp_version", `response echo ${echo} conflicts with served version metadata ${metadata}`);
394
+ return void 0;
395
+ }
396
+ const served = echo ?? metadata;
397
+ if (served === void 0) return configured;
398
+ if ((0, import_version2.compareProposalRefinementVersions)(served, ceiling) > 0) {
399
+ shape(
400
+ issues,
401
+ "adcp_version",
402
+ `response served AdCP ${served} above request ceiling ${ceiling} (configured schema ${configured})`
403
+ );
404
+ return void 0;
405
+ }
406
+ const sameMinor = served.split("-")[0] === configuredWire.split("-")[0];
407
+ const wireOnlyOverride = ceiling !== configuredWire && ceiling.split("-")[0] === configuredWire.split("-")[0];
408
+ if (served === configuredWire || wireOnlyOverride && served === ceiling && sameMinor) return configured;
409
+ if ((0, import_version2.compareProposalRefinementVersions)(served, configuredWire) > 0) {
410
+ shape(
411
+ issues,
412
+ "adcp_version",
413
+ `response served AdCP ${served}, newer than configured schema ${configured}; set adcpVersion to the served contract and register its matching schema bundle`
414
+ );
415
+ return void 0;
416
+ }
417
+ return metadata === served && options.servedAdcpVersion ? options.servedAdcpVersion : served;
418
+ }
419
+ function validateResponseShape(value, issues, adcpVersion, expectedVersion) {
351
420
  if (!isRecord(value)) {
352
421
  push(issues, "shape", "response", "response must be an object");
353
422
  return false;
354
423
  }
355
424
  let valid = allowedKeys(value, RESPONSE_KEYS, "response", issues);
356
- if (value.adcp_version !== void 0 && (typeof value.adcp_version !== "string" || !ADCP_32_RELEASE.test(value.adcp_version))) {
425
+ if (value.adcp_version !== void 0 && !(0, import_version2.isProposalRefinementVersion)(value.adcp_version)) {
357
426
  valid = shape(
358
427
  issues,
359
428
  "adcp_version",
360
- `refine_proposals response adcp_version must be on the 3.2 release line; received ${describeReceived(
429
+ `refine_proposals response adcp_version must be release-precision AdCP 3.2 or 3.3; received ${(0, import_version2.describeRefinementVersion)(
361
430
  value.adcp_version
362
431
  )}`
363
432
  );
364
433
  }
434
+ const selectedWireVersion = (0, import_version.toReleasePrecisionVersion)(adcpVersion);
435
+ if (!(0, import_version2.isProposalRefinementVersion)(selectedWireVersion)) {
436
+ return shape(
437
+ issues,
438
+ "adcp_version",
439
+ `selected schema version ${(0, import_version2.describeRefinementVersion)(adcpVersion)} must be AdCP 3.2 or 3.3`
440
+ );
441
+ }
442
+ if ((0, import_version2.isProposalRefinementVersion)(value.adcp_version) && (expectedVersion ? value.adcp_version !== expectedVersion : value.adcp_version.split("-")[0] !== selectedWireVersion.split("-")[0])) {
443
+ return shape(
444
+ issues,
445
+ "adcp_version",
446
+ `response adcp_version ${(0, import_version2.describeRefinementVersion)(value.adcp_version)} must match selected served schema ${adcpVersion}`
447
+ );
448
+ }
449
+ try {
450
+ const selected = (0, import_schema_loader.getSchemaDocumentByRef)("core/canonical-proposal.json", adcpVersion);
451
+ if (!selected) {
452
+ return shape(issues, "adcp_version", unavailableRefinementSchema(adcpVersion));
453
+ }
454
+ if ((0, import_version.toReleasePrecisionVersion)(selected.resolvedVersion) !== selectedWireVersion) {
455
+ return shape(
456
+ issues,
457
+ "adcp_version",
458
+ `selected proposal schema for AdCP ${adcpVersion} resolved to ${(0, import_version2.describeRefinementVersion)(selected.resolvedVersion)}; install or register the actual matching schema bundle`
459
+ );
460
+ }
461
+ } catch (error) {
462
+ return shape(issues, "adcp_version", unavailableRefinementSchema(adcpVersion, error));
463
+ }
365
464
  if (value.message !== void 0 && (typeof value.message !== "string" || value.message.length > 2e3)) {
366
465
  valid = shape(issues, "message", "message must be a string of at most 2000 characters");
367
466
  }
@@ -393,7 +492,7 @@ function validateResponseShape(value, issues) {
393
492
  valid = shape(issues, "results", "results must be a non-empty array");
394
493
  } else {
395
494
  value.results.forEach((result, index) => {
396
- if (!validateResultShape(result, `results[${index}]`, issues)) valid = false;
495
+ if (!validateResultShape(result, `results[${index}]`, issues, adcpVersion)) valid = false;
397
496
  });
398
497
  }
399
498
  if (!Array.isArray(value.products)) {
@@ -420,7 +519,7 @@ function validateAdvisoryErrors(value, issues) {
420
519
  });
421
520
  return valid;
422
521
  }
423
- function validateResultShape(value, path, issues) {
522
+ function validateResultShape(value, path, issues, adcpVersion) {
424
523
  if (!isRecord(value)) return shape(issues, path, "result must be an object");
425
524
  let valid = allowedKeys(value, RESULT_KEYS, path, issues);
426
525
  if (!nonempty(value.source_proposal_id))
@@ -437,14 +536,14 @@ function validateResultShape(value, path, issues) {
437
536
  };
438
537
  if (outcome === "revised") {
439
538
  forbidden(["proposal", "reason_code", "reason", "unsatisfied_constraints", "unsatisfied_product_changes"]);
440
- if (!validateProposalArray(value.proposals, `${path}.proposals`, issues)) valid = false;
539
+ if (!validateProposalArray(value.proposals, `${path}.proposals`, issues, adcpVersion)) valid = false;
441
540
  } else if (outcome === "partial") {
442
541
  forbidden(["proposal"]);
443
- if (!validateProposalArray(value.proposals, `${path}.proposals`, issues)) valid = false;
542
+ if (!validateProposalArray(value.proposals, `${path}.proposals`, issues, adcpVersion)) valid = false;
444
543
  if (!validateReason(value, path, issues)) valid = false;
445
544
  } else if (outcome === "finalized") {
446
545
  forbidden(["proposals", "reason_code", "reason", "unsatisfied_constraints", "unsatisfied_product_changes"]);
447
- if (!validateCanonicalProposalShape(value.proposal, `${path}.proposal`, issues)) valid = false;
546
+ if (!validateCanonicalProposalShape(value.proposal, `${path}.proposal`, issues, adcpVersion)) valid = false;
448
547
  } else {
449
548
  forbidden(["proposal", "proposals"]);
450
549
  if (!validateReason(value, path, issues)) valid = false;
@@ -486,24 +585,39 @@ function validateReason(value, path, issues) {
486
585
  if (!nonempty(value.reason)) valid = shape(issues, `${path}.reason`, "reason must be a non-empty string");
487
586
  return valid;
488
587
  }
489
- function validateProposalArray(value, path, issues) {
588
+ function validateProposalArray(value, path, issues, adcpVersion) {
490
589
  if (!Array.isArray(value) || value.length === 0) return shape(issues, path, "proposals must be a non-empty array");
491
590
  let valid = true;
492
591
  value.forEach((proposal, index) => {
493
- if (!validateCanonicalProposalShape(proposal, `${path}[${index}]`, issues)) valid = false;
592
+ if (!validateCanonicalProposalShape(proposal, `${path}[${index}]`, issues, adcpVersion)) valid = false;
494
593
  });
495
594
  return valid;
496
595
  }
497
- function validateCanonicalProposalShape(value, path, issues) {
498
- const validateCanonicalProposal = (0, import_schema_loader.getSchemaValidatorByRef)("core/canonical-proposal.json");
499
- if (!validateCanonicalProposal) return shape(issues, path, "canonical-proposal schema is unavailable");
596
+ function unavailableRefinementSchema(adcpVersion, error) {
597
+ const cause = error instanceof import_errors.ConfigurationError ? error.message.includes("Schema validation requires unambiguous schema identities") ? " (ConfigurationError: schema validation requires unambiguous schema identities)" : " (ConfigurationError)" : error === void 0 ? "" : error instanceof Error && /^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(error.name) ? ` (${error.name})` : " (schema loading error)";
598
+ return `selected proposal schema for AdCP ${adcpVersion} is unavailable${cause}; install its schema bundle or register a matching external schema root with @adcp/sdk/testing`;
599
+ }
600
+ function getRefinementSchemaValidator(schemaRef, adcpVersion) {
601
+ try {
602
+ const validator = (0, import_schema_loader.getSchemaValidatorByRef)(schemaRef, adcpVersion);
603
+ return { validator, error: validator ? void 0 : unavailableRefinementSchema(adcpVersion) };
604
+ } catch (error) {
605
+ return { validator: void 0, error: unavailableRefinementSchema(adcpVersion, error) };
606
+ }
607
+ }
608
+ function validateCanonicalProposalShape(value, path, issues, adcpVersion) {
609
+ const { validator: validateCanonicalProposal, error: schemaError } = getRefinementSchemaValidator(
610
+ "core/canonical-proposal.json",
611
+ adcpVersion
612
+ );
613
+ if (!validateCanonicalProposal) return shape(issues, path, schemaError);
500
614
  if (!validateCanonicalProposal(value)) {
501
615
  const error = validateCanonicalProposal.errors?.[0];
502
616
  const errorPath = error?.instancePath ? `${path}${error.instancePath.replaceAll("/", ".")}` : path;
503
617
  return shape(
504
618
  issues,
505
619
  errorPath,
506
- `proposal must satisfy the AdCP 3.2 schema${error?.message ? `: ${error.message}` : ""}`
620
+ `proposal must satisfy the selected AdCP ${adcpVersion} schema${error?.message ? `: ${error.message}` : ""}`
507
621
  );
508
622
  }
509
623
  const proposal = value;
@@ -547,10 +661,11 @@ function validateCanonicalProposalShape(value, path, issues) {
547
661
  if (proposal.proposal_status === "committed" && !isStrictDateTime(proposal.expires_at)) {
548
662
  valid = shape(issues, `${path}.expires_at`, "committed proposals require expires_at");
549
663
  }
550
- if (!validateCommercialTerms(proposal.commercial_terms, `${path}.commercial_terms`, issues)) valid = false;
664
+ if (!validateCommercialTerms(proposal.commercial_terms, `${path}.commercial_terms`, issues, adcpVersion))
665
+ valid = false;
551
666
  return valid;
552
667
  }
553
- function validateCommercialTerms(value, path, issues) {
668
+ function validateCommercialTerms(value, path, issues, adcpVersion) {
554
669
  if (!isRecord(value)) return shape(issues, path, "commercial_terms must be an object");
555
670
  let valid = true;
556
671
  if (!isRecord(value.brand)) valid = shape(issues, `${path}.brand`, "brand must be an object");
@@ -604,7 +719,7 @@ function validateCommercialTerms(value, path, issues) {
604
719
  valid = shape(issues, `${path}.reporting_commitments`, "reporting_commitments must be a non-empty array");
605
720
  if (value.cancellation_terms !== void 0 && !validateCancellationTerms(value.cancellation_terms, `${path}.cancellation_terms`, issues))
606
721
  valid = false;
607
- if (value.change_terms !== void 0 && !validateChangeTerms(value.change_terms, `${path}.change_terms`, issues, purchaseCurrency))
722
+ if (value.change_terms !== void 0 && !validateChangeTerms(value.change_terms, `${path}.change_terms`, issues, adcpVersion, purchaseCurrency))
608
723
  valid = false;
609
724
  return valid;
610
725
  }
@@ -620,10 +735,13 @@ const CHANGE_TERM_ACTIONS_BY_CONSTRAINT = {
620
735
  package_count: /* @__PURE__ */ new Set(["add_packages", "remove_packages"]),
621
736
  effective_timing: /* @__PURE__ */ new Set(["pause", "resume", "cancel"])
622
737
  };
623
- function validateChangeTerms(value, path, issues, purchaseCurrency) {
738
+ function validateChangeTerms(value, path, issues, adcpVersion, purchaseCurrency) {
624
739
  if (!Array.isArray(value) || value.length === 0) return shape(issues, path, "change_terms must be a non-empty array");
625
- const validateChangeTerm = (0, import_schema_loader.getSchemaValidatorByRef)("media-buy/change-term.json");
626
- if (!validateChangeTerm) return shape(issues, path, "change-term schema is unavailable");
740
+ const { validator: validateChangeTerm, error: schemaError } = getRefinementSchemaValidator(
741
+ "media-buy/change-term.json",
742
+ adcpVersion
743
+ );
744
+ if (!validateChangeTerm) return shape(issues, path, schemaError);
627
745
  let valid = true;
628
746
  const actions = /* @__PURE__ */ new Set();
629
747
  value.forEach((term, index) => {
@@ -634,7 +752,7 @@ function validateChangeTerms(value, path, issues, purchaseCurrency) {
634
752
  valid = shape(
635
753
  issues,
636
754
  errorPath,
637
- `change term must satisfy the AdCP 3.2 schema${error?.message ? `: ${error.message}` : ""}`
755
+ `change term must satisfy the selected AdCP ${adcpVersion} schema${error?.message ? `: ${error.message}` : ""}`
638
756
  );
639
757
  return;
640
758
  }
@@ -1341,6 +1459,7 @@ function push(issues, code, path, message) {
1341
1459
  assertRefineProposalsResponseShape,
1342
1460
  isStrictDateTime,
1343
1461
  proposalTermsDigest,
1462
+ selectRefinementSchemaVersion,
1344
1463
  validateRefineProposalsResponseShape,
1345
1464
  verifyProposalCommercialTerms,
1346
1465
  verifyProposalTermsDigest,
@@ -2,10 +2,11 @@ import { createHash } from "node:crypto";
2
2
  import { MAX_JSON_DEPTH } from "../utils/json-depth.mjs";
3
3
  import { canonicalize } from "../utils/jcs.mjs";
4
4
  import { getSchemaDocumentByRef, getSchemaValidatorByRef } from "../validation/schema-loader.mjs";
5
- import { ADCP_VERSION } from "../version.mjs";
5
+ import { ADCP_VERSION, toReleasePrecisionVersion } from "../version.mjs";
6
+ import { ConfigurationError } from "../errors/index.mjs";
7
+ import { compareProposalRefinementVersions, describeRefinementVersion, isProposalRefinementVersion } from "./version.mjs";
6
8
  import { commercialTermsSchemaSupportError } from "./commercial-terms-schema.mjs";
7
9
  import { COMMERCIAL_TERMS_KEYWORDS } from "./commercial-terms-keywords.mjs";
8
- const ADCP_32_RELEASE = /^3\.2(?:-[A-Za-z0-9]+(?:[.-][A-Za-z0-9]+)*)?$/;
9
10
  const MAX_COMMERCIAL_TERMS_BYTES = 256 * 1024;
10
11
  const MAX_COMMERCIAL_TERMS_NODES = 5e4;
11
12
  const MAX_COMMERCIAL_TERMS_MISMATCHES = 100;
@@ -263,7 +264,8 @@ function isStrictDateTime(value) {
263
264
  }
264
265
  function verifyRefineProposalsResponse(request, response, options = {}) {
265
266
  const issues = [];
266
- if (!validateResponseShape(response, issues)) return { ok: false, issues };
267
+ const adcpVersion = selectRefinementSchemaVersion(request.adcp_version, response, options, issues);
268
+ if (adcpVersion === void 0 || !validateResponseShape(response, issues, adcpVersion)) return { ok: false, issues };
267
269
  if (response.status === "submitted") {
268
270
  push(issues, "shape", "status", "semantic proposal verification requires a completed response");
269
271
  return { ok: false, issues };
@@ -305,30 +307,126 @@ function assertRefineProposalsResponse(request, response, options = {}) {
305
307
  const result = verifyRefineProposalsResponse(request, response, options);
306
308
  if (!result.ok) throw new ProposalResponseVerificationError(result.issues);
307
309
  }
308
- function validateRefineProposalsResponseShape(response) {
310
+ function validateRefineProposalsResponseShape(response, options = {}) {
309
311
  const issues = [];
310
- validateResponseShape(response, issues);
312
+ validateResponseShape(
313
+ response,
314
+ issues,
315
+ options.adcpVersion ?? ADCP_VERSION,
316
+ options.adcpVersion && toReleasePrecisionVersion(options.adcpVersion)
317
+ );
311
318
  return { ok: issues.length === 0, issues };
312
319
  }
313
- function assertRefineProposalsResponseShape(response) {
314
- const result = validateRefineProposalsResponseShape(response);
320
+ function assertRefineProposalsResponseShape(response, options = {}) {
321
+ const result = validateRefineProposalsResponseShape(response, options);
315
322
  if (!result.ok) throw new ProposalResponseVerificationError(result.issues);
316
323
  }
317
- function validateResponseShape(value, issues) {
324
+ function selectRefinementSchemaVersion(requested, response, options, issues) {
325
+ const configured = options.adcpVersion ?? ADCP_VERSION;
326
+ const configuredWire = toReleasePrecisionVersion(configured);
327
+ if (!isProposalRefinementVersion(configuredWire)) {
328
+ shape(issues, "adcp_version", `selected schema ${describeRefinementVersion(configured)} must be AdCP 3.2 or 3.3`);
329
+ return void 0;
330
+ }
331
+ const ceiling = requested ?? configuredWire;
332
+ if (!isProposalRefinementVersion(ceiling)) {
333
+ shape(
334
+ issues,
335
+ "adcp_version",
336
+ `request ceiling must be release-precision AdCP 3.2 or 3.3; received ${describeRefinementVersion(ceiling)}`
337
+ );
338
+ return void 0;
339
+ }
340
+ const echo = isRecord(response) ? response.adcp_version : void 0;
341
+ if (echo !== void 0 && !isProposalRefinementVersion(echo)) {
342
+ shape(
343
+ issues,
344
+ "adcp_version",
345
+ `response must echo release-precision AdCP 3.2 or 3.3 at or below request ceiling ${ceiling}; received ${describeRefinementVersion(echo)}`
346
+ );
347
+ return void 0;
348
+ }
349
+ const metadata = options.servedAdcpVersion === void 0 ? void 0 : toReleasePrecisionVersion(options.servedAdcpVersion);
350
+ if (metadata !== void 0 && !isProposalRefinementVersion(metadata)) {
351
+ shape(
352
+ issues,
353
+ "adcp_version",
354
+ `served version metadata must identify AdCP 3.2 or 3.3; received ${describeRefinementVersion(options.servedAdcpVersion)}`
355
+ );
356
+ return void 0;
357
+ }
358
+ if (metadata !== void 0 && echo !== void 0 && metadata !== echo) {
359
+ shape(issues, "adcp_version", `response echo ${echo} conflicts with served version metadata ${metadata}`);
360
+ return void 0;
361
+ }
362
+ const served = echo ?? metadata;
363
+ if (served === void 0) return configured;
364
+ if (compareProposalRefinementVersions(served, ceiling) > 0) {
365
+ shape(
366
+ issues,
367
+ "adcp_version",
368
+ `response served AdCP ${served} above request ceiling ${ceiling} (configured schema ${configured})`
369
+ );
370
+ return void 0;
371
+ }
372
+ const sameMinor = served.split("-")[0] === configuredWire.split("-")[0];
373
+ const wireOnlyOverride = ceiling !== configuredWire && ceiling.split("-")[0] === configuredWire.split("-")[0];
374
+ if (served === configuredWire || wireOnlyOverride && served === ceiling && sameMinor) return configured;
375
+ if (compareProposalRefinementVersions(served, configuredWire) > 0) {
376
+ shape(
377
+ issues,
378
+ "adcp_version",
379
+ `response served AdCP ${served}, newer than configured schema ${configured}; set adcpVersion to the served contract and register its matching schema bundle`
380
+ );
381
+ return void 0;
382
+ }
383
+ return metadata === served && options.servedAdcpVersion ? options.servedAdcpVersion : served;
384
+ }
385
+ function validateResponseShape(value, issues, adcpVersion, expectedVersion) {
318
386
  if (!isRecord(value)) {
319
387
  push(issues, "shape", "response", "response must be an object");
320
388
  return false;
321
389
  }
322
390
  let valid = allowedKeys(value, RESPONSE_KEYS, "response", issues);
323
- if (value.adcp_version !== void 0 && (typeof value.adcp_version !== "string" || !ADCP_32_RELEASE.test(value.adcp_version))) {
391
+ if (value.adcp_version !== void 0 && !isProposalRefinementVersion(value.adcp_version)) {
324
392
  valid = shape(
325
393
  issues,
326
394
  "adcp_version",
327
- `refine_proposals response adcp_version must be on the 3.2 release line; received ${describeReceived(
395
+ `refine_proposals response adcp_version must be release-precision AdCP 3.2 or 3.3; received ${describeRefinementVersion(
328
396
  value.adcp_version
329
397
  )}`
330
398
  );
331
399
  }
400
+ const selectedWireVersion = toReleasePrecisionVersion(adcpVersion);
401
+ if (!isProposalRefinementVersion(selectedWireVersion)) {
402
+ return shape(
403
+ issues,
404
+ "adcp_version",
405
+ `selected schema version ${describeRefinementVersion(adcpVersion)} must be AdCP 3.2 or 3.3`
406
+ );
407
+ }
408
+ if (isProposalRefinementVersion(value.adcp_version) && (expectedVersion ? value.adcp_version !== expectedVersion : value.adcp_version.split("-")[0] !== selectedWireVersion.split("-")[0])) {
409
+ return shape(
410
+ issues,
411
+ "adcp_version",
412
+ `response adcp_version ${describeRefinementVersion(value.adcp_version)} must match selected served schema ${adcpVersion}`
413
+ );
414
+ }
415
+ try {
416
+ const selected = getSchemaDocumentByRef("core/canonical-proposal.json", adcpVersion);
417
+ if (!selected) {
418
+ return shape(issues, "adcp_version", unavailableRefinementSchema(adcpVersion));
419
+ }
420
+ if (toReleasePrecisionVersion(selected.resolvedVersion) !== selectedWireVersion) {
421
+ return shape(
422
+ issues,
423
+ "adcp_version",
424
+ `selected proposal schema for AdCP ${adcpVersion} resolved to ${describeRefinementVersion(selected.resolvedVersion)}; install or register the actual matching schema bundle`
425
+ );
426
+ }
427
+ } catch (error) {
428
+ return shape(issues, "adcp_version", unavailableRefinementSchema(adcpVersion, error));
429
+ }
332
430
  if (value.message !== void 0 && (typeof value.message !== "string" || value.message.length > 2e3)) {
333
431
  valid = shape(issues, "message", "message must be a string of at most 2000 characters");
334
432
  }
@@ -360,7 +458,7 @@ function validateResponseShape(value, issues) {
360
458
  valid = shape(issues, "results", "results must be a non-empty array");
361
459
  } else {
362
460
  value.results.forEach((result, index) => {
363
- if (!validateResultShape(result, `results[${index}]`, issues)) valid = false;
461
+ if (!validateResultShape(result, `results[${index}]`, issues, adcpVersion)) valid = false;
364
462
  });
365
463
  }
366
464
  if (!Array.isArray(value.products)) {
@@ -387,7 +485,7 @@ function validateAdvisoryErrors(value, issues) {
387
485
  });
388
486
  return valid;
389
487
  }
390
- function validateResultShape(value, path, issues) {
488
+ function validateResultShape(value, path, issues, adcpVersion) {
391
489
  if (!isRecord(value)) return shape(issues, path, "result must be an object");
392
490
  let valid = allowedKeys(value, RESULT_KEYS, path, issues);
393
491
  if (!nonempty(value.source_proposal_id))
@@ -404,14 +502,14 @@ function validateResultShape(value, path, issues) {
404
502
  };
405
503
  if (outcome === "revised") {
406
504
  forbidden(["proposal", "reason_code", "reason", "unsatisfied_constraints", "unsatisfied_product_changes"]);
407
- if (!validateProposalArray(value.proposals, `${path}.proposals`, issues)) valid = false;
505
+ if (!validateProposalArray(value.proposals, `${path}.proposals`, issues, adcpVersion)) valid = false;
408
506
  } else if (outcome === "partial") {
409
507
  forbidden(["proposal"]);
410
- if (!validateProposalArray(value.proposals, `${path}.proposals`, issues)) valid = false;
508
+ if (!validateProposalArray(value.proposals, `${path}.proposals`, issues, adcpVersion)) valid = false;
411
509
  if (!validateReason(value, path, issues)) valid = false;
412
510
  } else if (outcome === "finalized") {
413
511
  forbidden(["proposals", "reason_code", "reason", "unsatisfied_constraints", "unsatisfied_product_changes"]);
414
- if (!validateCanonicalProposalShape(value.proposal, `${path}.proposal`, issues)) valid = false;
512
+ if (!validateCanonicalProposalShape(value.proposal, `${path}.proposal`, issues, adcpVersion)) valid = false;
415
513
  } else {
416
514
  forbidden(["proposal", "proposals"]);
417
515
  if (!validateReason(value, path, issues)) valid = false;
@@ -453,24 +551,39 @@ function validateReason(value, path, issues) {
453
551
  if (!nonempty(value.reason)) valid = shape(issues, `${path}.reason`, "reason must be a non-empty string");
454
552
  return valid;
455
553
  }
456
- function validateProposalArray(value, path, issues) {
554
+ function validateProposalArray(value, path, issues, adcpVersion) {
457
555
  if (!Array.isArray(value) || value.length === 0) return shape(issues, path, "proposals must be a non-empty array");
458
556
  let valid = true;
459
557
  value.forEach((proposal, index) => {
460
- if (!validateCanonicalProposalShape(proposal, `${path}[${index}]`, issues)) valid = false;
558
+ if (!validateCanonicalProposalShape(proposal, `${path}[${index}]`, issues, adcpVersion)) valid = false;
461
559
  });
462
560
  return valid;
463
561
  }
464
- function validateCanonicalProposalShape(value, path, issues) {
465
- const validateCanonicalProposal = getSchemaValidatorByRef("core/canonical-proposal.json");
466
- if (!validateCanonicalProposal) return shape(issues, path, "canonical-proposal schema is unavailable");
562
+ function unavailableRefinementSchema(adcpVersion, error) {
563
+ const cause = error instanceof ConfigurationError ? error.message.includes("Schema validation requires unambiguous schema identities") ? " (ConfigurationError: schema validation requires unambiguous schema identities)" : " (ConfigurationError)" : error === void 0 ? "" : error instanceof Error && /^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(error.name) ? ` (${error.name})` : " (schema loading error)";
564
+ return `selected proposal schema for AdCP ${adcpVersion} is unavailable${cause}; install its schema bundle or register a matching external schema root with @adcp/sdk/testing`;
565
+ }
566
+ function getRefinementSchemaValidator(schemaRef, adcpVersion) {
567
+ try {
568
+ const validator = getSchemaValidatorByRef(schemaRef, adcpVersion);
569
+ return { validator, error: validator ? void 0 : unavailableRefinementSchema(adcpVersion) };
570
+ } catch (error) {
571
+ return { validator: void 0, error: unavailableRefinementSchema(adcpVersion, error) };
572
+ }
573
+ }
574
+ function validateCanonicalProposalShape(value, path, issues, adcpVersion) {
575
+ const { validator: validateCanonicalProposal, error: schemaError } = getRefinementSchemaValidator(
576
+ "core/canonical-proposal.json",
577
+ adcpVersion
578
+ );
579
+ if (!validateCanonicalProposal) return shape(issues, path, schemaError);
467
580
  if (!validateCanonicalProposal(value)) {
468
581
  const error = validateCanonicalProposal.errors?.[0];
469
582
  const errorPath = error?.instancePath ? `${path}${error.instancePath.replaceAll("/", ".")}` : path;
470
583
  return shape(
471
584
  issues,
472
585
  errorPath,
473
- `proposal must satisfy the AdCP 3.2 schema${error?.message ? `: ${error.message}` : ""}`
586
+ `proposal must satisfy the selected AdCP ${adcpVersion} schema${error?.message ? `: ${error.message}` : ""}`
474
587
  );
475
588
  }
476
589
  const proposal = value;
@@ -514,10 +627,11 @@ function validateCanonicalProposalShape(value, path, issues) {
514
627
  if (proposal.proposal_status === "committed" && !isStrictDateTime(proposal.expires_at)) {
515
628
  valid = shape(issues, `${path}.expires_at`, "committed proposals require expires_at");
516
629
  }
517
- if (!validateCommercialTerms(proposal.commercial_terms, `${path}.commercial_terms`, issues)) valid = false;
630
+ if (!validateCommercialTerms(proposal.commercial_terms, `${path}.commercial_terms`, issues, adcpVersion))
631
+ valid = false;
518
632
  return valid;
519
633
  }
520
- function validateCommercialTerms(value, path, issues) {
634
+ function validateCommercialTerms(value, path, issues, adcpVersion) {
521
635
  if (!isRecord(value)) return shape(issues, path, "commercial_terms must be an object");
522
636
  let valid = true;
523
637
  if (!isRecord(value.brand)) valid = shape(issues, `${path}.brand`, "brand must be an object");
@@ -571,7 +685,7 @@ function validateCommercialTerms(value, path, issues) {
571
685
  valid = shape(issues, `${path}.reporting_commitments`, "reporting_commitments must be a non-empty array");
572
686
  if (value.cancellation_terms !== void 0 && !validateCancellationTerms(value.cancellation_terms, `${path}.cancellation_terms`, issues))
573
687
  valid = false;
574
- if (value.change_terms !== void 0 && !validateChangeTerms(value.change_terms, `${path}.change_terms`, issues, purchaseCurrency))
688
+ if (value.change_terms !== void 0 && !validateChangeTerms(value.change_terms, `${path}.change_terms`, issues, adcpVersion, purchaseCurrency))
575
689
  valid = false;
576
690
  return valid;
577
691
  }
@@ -587,10 +701,13 @@ const CHANGE_TERM_ACTIONS_BY_CONSTRAINT = {
587
701
  package_count: /* @__PURE__ */ new Set(["add_packages", "remove_packages"]),
588
702
  effective_timing: /* @__PURE__ */ new Set(["pause", "resume", "cancel"])
589
703
  };
590
- function validateChangeTerms(value, path, issues, purchaseCurrency) {
704
+ function validateChangeTerms(value, path, issues, adcpVersion, purchaseCurrency) {
591
705
  if (!Array.isArray(value) || value.length === 0) return shape(issues, path, "change_terms must be a non-empty array");
592
- const validateChangeTerm = getSchemaValidatorByRef("media-buy/change-term.json");
593
- if (!validateChangeTerm) return shape(issues, path, "change-term schema is unavailable");
706
+ const { validator: validateChangeTerm, error: schemaError } = getRefinementSchemaValidator(
707
+ "media-buy/change-term.json",
708
+ adcpVersion
709
+ );
710
+ if (!validateChangeTerm) return shape(issues, path, schemaError);
594
711
  let valid = true;
595
712
  const actions = /* @__PURE__ */ new Set();
596
713
  value.forEach((term, index) => {
@@ -601,7 +718,7 @@ function validateChangeTerms(value, path, issues, purchaseCurrency) {
601
718
  valid = shape(
602
719
  issues,
603
720
  errorPath,
604
- `change term must satisfy the AdCP 3.2 schema${error?.message ? `: ${error.message}` : ""}`
721
+ `change term must satisfy the selected AdCP ${adcpVersion} schema${error?.message ? `: ${error.message}` : ""}`
605
722
  );
606
723
  return;
607
724
  }
@@ -1307,6 +1424,7 @@ export {
1307
1424
  assertRefineProposalsResponseShape,
1308
1425
  isStrictDateTime,
1309
1426
  proposalTermsDigest,
1427
+ selectRefinementSchemaVersion,
1310
1428
  validateRefineProposalsResponseShape,
1311
1429
  verifyProposalCommercialTerms,
1312
1430
  verifyProposalTermsDigest,
@@ -0,0 +1,6 @@
1
+ import type { ProposalRefinementVersion } from './types.mjs';
2
+ /** Supported wire releases. Keep this guard and the public type in sync. */
3
+ export declare function isProposalRefinementVersion(value: unknown): value is ProposalRefinementVersion;
4
+ /** Inputs have already passed the release-precision guard above. */
5
+ export declare function compareProposalRefinementVersions(left: ProposalRefinementVersion, right: ProposalRefinementVersion): number;
6
+ export declare function describeRefinementVersion(value: unknown): string;
@@ -0,0 +1,6 @@
1
+ import type { ProposalRefinementVersion } from './types';
2
+ /** Supported wire releases. Keep this guard and the public type in sync. */
3
+ export declare function isProposalRefinementVersion(value: unknown): value is ProposalRefinementVersion;
4
+ /** Inputs have already passed the release-precision guard above. */
5
+ export declare function compareProposalRefinementVersions(left: ProposalRefinementVersion, right: ProposalRefinementVersion): number;
6
+ export declare function describeRefinementVersion(value: unknown): string;