@adcp/sdk 13.0.0-rc.10 → 13.0.0-rc.13

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 (116) hide show
  1. package/dist/lib/core/AgentClient.d.mts +12 -3
  2. package/dist/lib/core/AgentClient.d.ts +12 -3
  3. package/dist/lib/core/AgentClient.d.ts.map +1 -1
  4. package/dist/lib/core/AgentClient.js +12 -3
  5. package/dist/lib/core/AgentClient.js.map +1 -1
  6. package/dist/lib/core/AgentClient.mjs +12 -3
  7. package/dist/lib/core/AgentClient.mjs.map +1 -1
  8. package/dist/lib/core/SingleAgentClient.d.mts +13 -3
  9. package/dist/lib/core/SingleAgentClient.d.ts +13 -3
  10. package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
  11. package/dist/lib/core/SingleAgentClient.js +52 -31
  12. package/dist/lib/core/SingleAgentClient.js.map +1 -1
  13. package/dist/lib/core/SingleAgentClient.mjs +52 -31
  14. package/dist/lib/core/SingleAgentClient.mjs.map +1 -1
  15. package/dist/lib/index.d.mts +1 -1
  16. package/dist/lib/index.d.ts +1 -1
  17. package/dist/lib/index.d.ts.map +1 -1
  18. package/dist/lib/index.js +8 -0
  19. package/dist/lib/index.js.map +1 -1
  20. package/dist/lib/index.mjs +8 -0
  21. package/dist/lib/index.mjs.map +1 -1
  22. package/dist/lib/registry/types.generated.d.mts +48 -1
  23. package/dist/lib/registry/types.generated.d.ts +48 -1
  24. package/dist/lib/registry/types.generated.d.ts.map +1 -1
  25. package/dist/lib/registry/types.generated.js.map +1 -1
  26. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  27. package/dist/lib/server/decisioning/runtime/from-platform.d.mts +1 -1
  28. package/dist/lib/server/decisioning/runtime/from-platform.d.ts +1 -1
  29. package/dist/lib/server/decisioning/runtime/from-platform.d.ts.map +1 -1
  30. package/dist/lib/server/decisioning/runtime/from-platform.js +389 -65
  31. package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
  32. package/dist/lib/server/decisioning/runtime/from-platform.mjs +389 -65
  33. package/dist/lib/server/decisioning/runtime/from-platform.mjs.map +1 -1
  34. package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
  35. package/dist/lib/testing/compliance/comply.js +8 -2
  36. package/dist/lib/testing/compliance/comply.js.map +1 -1
  37. package/dist/lib/testing/compliance/comply.mjs +8 -2
  38. package/dist/lib/testing/compliance/comply.mjs.map +1 -1
  39. package/dist/lib/testing/compliance/spec-conformance.d.ts.map +1 -1
  40. package/dist/lib/testing/compliance/spec-conformance.js +2 -0
  41. package/dist/lib/testing/compliance/spec-conformance.js.map +1 -1
  42. package/dist/lib/testing/compliance/spec-conformance.mjs +2 -0
  43. package/dist/lib/testing/compliance/spec-conformance.mjs.map +1 -1
  44. package/dist/lib/testing/compliance/summary.d.mts +2 -0
  45. package/dist/lib/testing/compliance/summary.d.ts +2 -0
  46. package/dist/lib/testing/compliance/summary.d.ts.map +1 -1
  47. package/dist/lib/testing/compliance/summary.js +3 -2
  48. package/dist/lib/testing/compliance/summary.js.map +1 -1
  49. package/dist/lib/testing/compliance/summary.mjs +3 -2
  50. package/dist/lib/testing/compliance/summary.mjs.map +1 -1
  51. package/dist/lib/testing/compliance/types.d.mts +4 -0
  52. package/dist/lib/testing/compliance/types.d.ts +4 -0
  53. package/dist/lib/testing/compliance/types.d.ts.map +1 -1
  54. package/dist/lib/testing/compliance/types.js.map +1 -1
  55. package/dist/lib/testing/storyboard/junit.js +49 -32
  56. package/dist/lib/testing/storyboard/junit.js.map +1 -1
  57. package/dist/lib/testing/storyboard/junit.mjs +49 -32
  58. package/dist/lib/testing/storyboard/junit.mjs.map +1 -1
  59. package/dist/lib/testing/storyboard/loader.d.ts.map +1 -1
  60. package/dist/lib/testing/storyboard/loader.js +26 -0
  61. package/dist/lib/testing/storyboard/loader.js.map +1 -1
  62. package/dist/lib/testing/storyboard/loader.mjs +26 -0
  63. package/dist/lib/testing/storyboard/loader.mjs.map +1 -1
  64. package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
  65. package/dist/lib/testing/storyboard/runner.js +66 -15
  66. package/dist/lib/testing/storyboard/runner.js.map +1 -1
  67. package/dist/lib/testing/storyboard/runner.mjs +70 -16
  68. package/dist/lib/testing/storyboard/runner.mjs.map +1 -1
  69. package/dist/lib/testing/storyboard/types.d.mts +26 -1
  70. package/dist/lib/testing/storyboard/types.d.ts +26 -1
  71. package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
  72. package/dist/lib/testing/storyboard/types.js.map +1 -1
  73. package/dist/lib/testing/storyboard/types.mjs.map +1 -1
  74. package/dist/lib/testing/storyboard/validations.d.mts +11 -0
  75. package/dist/lib/testing/storyboard/validations.d.ts +11 -0
  76. package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
  77. package/dist/lib/testing/storyboard/validations.js +30 -13
  78. package/dist/lib/testing/storyboard/validations.js.map +1 -1
  79. package/dist/lib/testing/storyboard/validations.mjs +27 -13
  80. package/dist/lib/testing/storyboard/validations.mjs.map +1 -1
  81. package/dist/lib/v2/projection/augment-response.d.mts +45 -0
  82. package/dist/lib/v2/projection/augment-response.d.ts +45 -0
  83. package/dist/lib/v2/projection/augment-response.d.ts.map +1 -1
  84. package/dist/lib/v2/projection/augment-response.js +54 -0
  85. package/dist/lib/v2/projection/augment-response.js.map +1 -1
  86. package/dist/lib/v2/projection/augment-response.mjs +52 -0
  87. package/dist/lib/v2/projection/augment-response.mjs.map +1 -1
  88. package/dist/lib/v2/projection/index.d.mts +7 -1
  89. package/dist/lib/v2/projection/index.d.ts +7 -1
  90. package/dist/lib/v2/projection/index.d.ts.map +1 -1
  91. package/dist/lib/v2/projection/index.js +9 -0
  92. package/dist/lib/v2/projection/index.js.map +1 -1
  93. package/dist/lib/v2/projection/index.mjs +10 -0
  94. package/dist/lib/v2/projection/index.mjs.map +1 -1
  95. package/dist/lib/v2/projection/legacy-routes.d.mts +24 -0
  96. package/dist/lib/v2/projection/legacy-routes.d.ts +25 -0
  97. package/dist/lib/v2/projection/legacy-routes.d.ts.map +1 -0
  98. package/dist/lib/v2/projection/legacy-routes.js +163 -0
  99. package/dist/lib/v2/projection/legacy-routes.js.map +1 -0
  100. package/dist/lib/v2/projection/legacy-routes.mjs +138 -0
  101. package/dist/lib/v2/projection/legacy-routes.mjs.map +1 -0
  102. package/dist/lib/v2/projection/v1-to-v2.d.mts +3 -0
  103. package/dist/lib/v2/projection/v1-to-v2.d.ts +3 -0
  104. package/dist/lib/v2/projection/v1-to-v2.d.ts.map +1 -1
  105. package/dist/lib/v2/projection/v1-to-v2.js +2 -1
  106. package/dist/lib/v2/projection/v1-to-v2.js.map +1 -1
  107. package/dist/lib/v2/projection/v1-to-v2.mjs +2 -1
  108. package/dist/lib/v2/projection/v1-to-v2.mjs.map +1 -1
  109. package/dist/lib/version.d.mts +3 -3
  110. package/dist/lib/version.d.ts +3 -3
  111. package/dist/lib/version.js +3 -3
  112. package/dist/lib/version.js.map +1 -1
  113. package/dist/lib/version.mjs +3 -3
  114. package/dist/lib/version.mjs.map +1 -1
  115. package/docs/migration-12-to-13.md +56 -2
  116. package/package.json +3 -1
@@ -71,6 +71,7 @@ import {
71
71
  projectMediaBuyCreativesForDelivery,
72
72
  stripLegacyCreativeIdentity
73
73
  } from "../../../v2/projection/creative-delivery.mjs";
74
+ import { projectV1ProductToV2 } from "../../../v2/projection/v1-to-v2.mjs";
74
75
  import { toCanonicalOnlyResponse } from "../../../v2/projection/augment-response.mjs";
75
76
  import {
76
77
  CanonicalFormatLegacyResolutionError,
@@ -78,6 +79,7 @@ import {
78
79
  resolveCanonicalFormatLegacyRefs
79
80
  } from "../../../v2/projection/v2-to-v1.mjs";
80
81
  import { legacyFormatRefsForDeclaration } from "../../../v2/projection/legacy-metadata.mjs";
82
+ import { canonicalizeAgentUrl } from "../../../discovery/resolve-agent-properties.mjs";
81
83
  import { ADCP_VERSION } from "../../../version.mjs";
82
84
  function supportsCanonicalCreativeCapability(version) {
83
85
  if (typeof version !== "string") return false;
@@ -95,6 +97,21 @@ function requiresCanonicalCreativeWire(version) {
95
97
  const minor = Number(match[2]);
96
98
  return major > 3 || major === 3 && minor >= 2;
97
99
  }
100
+ function supportsTransitionalDualCreativeWire(version) {
101
+ if (typeof version !== "string") return false;
102
+ const match = /^v?(\d+)\.(\d+)(?:\.|-|$)/.exec(version.trim());
103
+ if (!match) return false;
104
+ return Number(match[1]) === 3 && Number(match[2]) === 1;
105
+ }
106
+ function shouldEmitTransitionalDualCreativeWire(version, request) {
107
+ if (!supportsTransitionalDualCreativeWire(version)) return false;
108
+ if (request === null || typeof request !== "object") return true;
109
+ const fields = Object.getOwnPropertyDescriptor(request, "fields");
110
+ if (!fields || !("value" in fields) || !Array.isArray(fields.value)) return true;
111
+ return Object.values(Object.getOwnPropertyDescriptors(fields.value)).some(
112
+ (descriptor) => descriptor.enumerable && "value" in descriptor && descriptor.value === "format_ids"
113
+ );
114
+ }
98
115
  function creativeWireEvidenceForRequest(request) {
99
116
  let canonical = false;
100
117
  let legacy = false;
@@ -366,7 +383,7 @@ function asCanonicalSemanticServerResponse(response, operation, legacyFormatConv
366
383
  }
367
384
  function asSemanticServerResponseForWire(response, operation, legacyFormatConverter, canonicalFormatLegacyResolver, wireMode) {
368
385
  if (wireMode === "canonical") {
369
- return attachCanonicalCustomFormatWireRefs(
386
+ return attachCanonicalFormatWireRefs(
370
387
  asCanonicalSemanticServerResponse(response, operation, legacyFormatConverter, canonicalFormatLegacyResolver),
371
388
  canonicalFormatLegacyResolver,
372
389
  operation
@@ -470,10 +487,10 @@ function asCanonicalGetProductsRequest(request) {
470
487
  ];
471
488
  return { ...request, fields };
472
489
  }
473
- function asCanonicalListCreativesRequest(request, legacyFormatConverter) {
490
+ function asCanonicalListCreativesRequest(request, legacyFormatConverter, responseWireMode) {
474
491
  const filters = request["filters"] !== null && typeof request["filters"] === "object" && !Array.isArray(request["filters"]) ? request["filters"] : void 0;
475
492
  const legacyFormatIds = filters?.["format_ids"];
476
- if (Array.isArray(legacyFormatIds) && legacyFormatIds.length > 0) {
493
+ if (Array.isArray(legacyFormatIds) && legacyFormatIds.length > 0 && responseWireMode !== "legacy") {
477
494
  throw new AdcpError("INVALID_REQUEST", {
478
495
  message: "list_creatives legacy format_ids filters are unavailable on the canonical platform surface.",
479
496
  field: "filters.format_ids",
@@ -488,15 +505,22 @@ function asCanonicalListCreativesRequest(request, legacyFormatConverter) {
488
505
  "list_creatives",
489
506
  legacyFormatConverter
490
507
  );
491
- if (!Array.isArray(projected["fields"])) return projected;
508
+ const withLegacyFilter = responseWireMode === "legacy" && Array.isArray(legacyFormatIds) && legacyFormatIds.length > 0 ? {
509
+ ...projected,
510
+ filters: {
511
+ ...projected["filters"] ?? {},
512
+ format_ids: legacyFormatIds
513
+ }
514
+ } : projected;
515
+ if (!Array.isArray(withLegacyFilter["fields"])) return withLegacyFilter;
492
516
  const fields = [
493
517
  ...new Set(
494
- projected["fields"].flatMap(
518
+ withLegacyFilter["fields"].flatMap(
495
519
  (field) => field === "format_id" ? ["format_kind"] : typeof field === "string" ? [field] : []
496
520
  )
497
521
  )
498
522
  ];
499
- return { ...projected, fields };
523
+ return { ...withLegacyFilter, fields };
500
524
  }
501
525
  function asCanonicalProductResponse(response, legacyFormatConverter) {
502
526
  try {
@@ -509,41 +533,324 @@ function asCanonicalProductResponse(response, legacyFormatConverter) {
509
533
  suggestion: "Return an acyclic data-only product response without enumerable accessors."
510
534
  });
511
535
  }
536
+ validateDualProductFormatDeclarations(response, legacyFormatConverter);
512
537
  const projected = toCanonicalOnlyResponse(response, {
513
538
  legacyFormatConverter
514
539
  });
515
- if (projected.diagnostics.length > 0) {
516
- const first = projected.diagnostics[0];
540
+ const blockingDiagnostics = projected.diagnostics.filter(
541
+ (diagnostic) => diagnostic.code !== "LEGACY_FORMAT_ID_DROPPED_UNMAPPED"
542
+ );
543
+ if (blockingDiagnostics.length > 0) {
544
+ const first = blockingDiagnostics[0];
517
545
  throw new AdcpError("INVALID_REQUEST", {
518
546
  message: "get_products returned a legacy format that cannot be represented canonically.",
519
547
  field: first.field,
520
548
  suggestion: "Declare canonical format_options or configure legacyCreativeFormatConverter for the legacy format."
521
549
  });
522
550
  }
523
- return asCanonicalServerResponse(projected.response);
551
+ const canonicalResponse = asCanonicalServerResponse(projected.response);
552
+ const resolvedDropDiagnostics = projected.diagnostics.filter(
553
+ (diagnostic) => diagnostic.code === "LEGACY_FORMAT_ID_DROPPED_UNMAPPED"
554
+ );
555
+ if (resolvedDropDiagnostics.length > 0 && canonicalResponse !== null && typeof canonicalResponse === "object") {
556
+ resolvedLegacyDropDiagnosticsByResponse.set(canonicalResponse, resolvedDropDiagnostics);
557
+ }
558
+ preserveAuthoredFormatIds(response, canonicalResponse);
559
+ return canonicalResponse;
560
+ }
561
+ const authoredFormatIdsByOwner = /* @__PURE__ */ new WeakMap();
562
+ const resolvedLegacyDropDiagnosticsByResponse = /* @__PURE__ */ new WeakMap();
563
+ function ownArrayDataValues(value) {
564
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
565
+ const length = lengthDescriptor && "value" in lengthDescriptor ? lengthDescriptor.value : 0;
566
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0) return [];
567
+ const values = [];
568
+ for (let index = 0; index < length; index++) {
569
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
570
+ if (descriptor?.enumerable && "value" in descriptor) values.push(descriptor.value);
571
+ }
572
+ return values;
573
+ }
574
+ function cloneWireData(value) {
575
+ if (Array.isArray(value)) {
576
+ const cloned2 = [];
577
+ for (const entry of ownArrayDataValues(value)) cloned2.push(cloneWireData(entry));
578
+ return cloned2;
579
+ }
580
+ if (value === null || typeof value !== "object") return value;
581
+ const cloned = {};
582
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {
583
+ if (descriptor.enumerable && "value" in descriptor) cloned[key] = cloneWireData(descriptor.value);
584
+ }
585
+ return cloned;
586
+ }
587
+ function preserveAuthoredFormatIds(source, target) {
588
+ if (source === null || target === null || typeof source !== "object" || typeof target !== "object") return;
589
+ const sourceProducts = Object.getOwnPropertyDescriptor(source, "products");
590
+ const targetProducts = Object.getOwnPropertyDescriptor(target, "products");
591
+ if (!sourceProducts || !sourceProducts.enumerable || !("value" in sourceProducts) || !Array.isArray(sourceProducts.value) || !targetProducts || !targetProducts.enumerable || !("value" in targetProducts) || !Array.isArray(targetProducts.value)) {
592
+ return;
593
+ }
594
+ const sourceValues = ownArrayDataValues(sourceProducts.value);
595
+ const targetValues = ownArrayDataValues(targetProducts.value);
596
+ const count = Math.min(sourceValues.length, targetValues.length);
597
+ for (let index = 0; index < count; index++)
598
+ preserveAuthoredFormatIdsForOwner(sourceValues[index], targetValues[index]);
599
+ }
600
+ function preserveAuthoredFormatIdsForOwner(source, target) {
601
+ if (source === null || target === null || typeof source !== "object" || typeof target !== "object") return;
602
+ const sourceProductId = Object.getOwnPropertyDescriptor(source, "product_id");
603
+ const sourcePlacementId = Object.getOwnPropertyDescriptor(source, "placement_id");
604
+ const recognizedOwner = sourceProductId?.enumerable && "value" in sourceProductId && typeof sourceProductId.value === "string" || sourcePlacementId?.enumerable && "value" in sourcePlacementId && typeof sourcePlacementId.value === "string";
605
+ if (!recognizedOwner) return;
606
+ const sourceFormatIds = Object.getOwnPropertyDescriptor(source, "format_ids");
607
+ const targetFormatOptions = Object.getOwnPropertyDescriptor(target, "format_options");
608
+ if (sourceFormatIds && sourceFormatIds.enumerable && "value" in sourceFormatIds && Array.isArray(sourceFormatIds.value) && targetFormatOptions && targetFormatOptions.enumerable && "value" in targetFormatOptions && Array.isArray(targetFormatOptions.value)) {
609
+ authoredFormatIdsByOwner.set(
610
+ target,
611
+ ownArrayDataValues(sourceFormatIds.value).map((ref) => cloneWireData(ref))
612
+ );
613
+ }
614
+ const sourcePlacements = Object.getOwnPropertyDescriptor(source, "placements");
615
+ const targetPlacements = Object.getOwnPropertyDescriptor(target, "placements");
616
+ if (sourcePlacements && sourcePlacements.enumerable && "value" in sourcePlacements && Array.isArray(sourcePlacements.value) && targetPlacements && targetPlacements.enumerable && "value" in targetPlacements && Array.isArray(targetPlacements.value)) {
617
+ const sourceValues = ownArrayDataValues(sourcePlacements.value);
618
+ const targetValues = ownArrayDataValues(targetPlacements.value);
619
+ const count = Math.min(sourceValues.length, targetValues.length);
620
+ for (let index = 0; index < count; index++) {
621
+ preserveAuthoredFormatIdsForOwner(sourceValues[index], targetValues[index]);
622
+ }
623
+ }
524
624
  }
525
- function attachCanonicalCustomFormatWireRefs(value, canonicalFormatLegacyResolver, operation) {
625
+ function removeResolvedLegacyDropErrors(response, diagnostics) {
626
+ if (response === null || typeof response !== "object") return response;
627
+ const resolved = diagnostics.filter((diagnostic) => diagnostic.code === "LEGACY_FORMAT_ID_DROPPED_UNMAPPED");
628
+ if (resolved.length === 0) return response;
629
+ const record = response;
630
+ if (!Array.isArray(record.errors)) return response;
631
+ const retained = [];
632
+ for (const value of ownArrayDataValues(record.errors)) {
633
+ let matchesResolvedDiagnostic = false;
634
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
635
+ const error = value;
636
+ for (const diagnostic of resolved) {
637
+ if (error.code === diagnostic.code && error.field === diagnostic.field && error.source === diagnostic.source && error.sdk_id === diagnostic.sdk_id && jsonValuesEqual(error.details, diagnostic.error.details)) {
638
+ matchesResolvedDiagnostic = true;
639
+ break;
640
+ }
641
+ }
642
+ }
643
+ if (!matchesResolvedDiagnostic) retained.push(value);
644
+ }
645
+ if (retained.length > 0) return { ...record, errors: retained };
646
+ const { errors: _resolvedErrors, ...withoutErrors } = record;
647
+ void _resolvedErrors;
648
+ return withoutErrors;
649
+ }
650
+ function legacyFormatRefKey(value) {
651
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
652
+ const ref = value;
653
+ if (typeof ref.agent_url !== "string" || typeof ref.id !== "string") return void 0;
654
+ const agentUrl = canonicalizeAgentUrl(ref.agent_url) ?? ref.agent_url;
655
+ return `${agentUrl}::${ref.id}::${String(ref.width ?? "")}x${String(ref.height ?? "")}::${String(ref.duration_ms ?? "")}`;
656
+ }
657
+ function jsonValuesEqual(left, right) {
658
+ if (Object.is(left, right)) return true;
659
+ if (Array.isArray(left) || Array.isArray(right)) {
660
+ if (!Array.isArray(left) || !Array.isArray(right)) return false;
661
+ const leftValues = ownArrayDataValues(left);
662
+ const rightValues = ownArrayDataValues(right);
663
+ if (leftValues.length !== rightValues.length) return false;
664
+ for (let index = 0; index < leftValues.length; index++) {
665
+ if (!jsonValuesEqual(leftValues[index], rightValues[index])) return false;
666
+ }
667
+ return true;
668
+ }
669
+ if (left === null || right === null || typeof left !== "object" || typeof right !== "object") return false;
670
+ const leftRecord = left;
671
+ const rightRecord = right;
672
+ const leftKeys = Object.keys(leftRecord);
673
+ const rightKeys = Object.keys(rightRecord);
674
+ return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(rightRecord, key) && jsonValuesEqual(leftRecord[key], rightRecord[key]));
675
+ }
676
+ function matchLegacyRefToDeclaration(declaration, ref, ownerId, legacyFormatConverter) {
677
+ const projected = projectV1ProductToV2(
678
+ {
679
+ product_id: ownerId,
680
+ name: ownerId,
681
+ description: ownerId,
682
+ format_ids: [ref]
683
+ },
684
+ { legacyFormatConverter }
685
+ );
686
+ if (projected.diagnostics.length > 0 || projected.v2.format_options.length !== 1) return "unresolved";
687
+ const expected = projected.v2.format_options[0];
688
+ if (declaration.format_kind !== expected.format_kind) return "kind_mismatch";
689
+ const params = declaration.params !== null && typeof declaration.params === "object" && !Array.isArray(declaration.params) ? declaration.params : {};
690
+ for (const [key, expectedValue] of Object.entries(expected.params)) {
691
+ if (!Object.hasOwn(params, key) || !jsonValuesEqual(params[key], expectedValue)) return "params_mismatch";
692
+ }
693
+ return "match";
694
+ }
695
+ function validateLegacyRefNarrowing(declaration, ref, ownerId, field, legacyFormatConverter) {
696
+ const match = matchLegacyRefToDeclaration(declaration, ref, ownerId, legacyFormatConverter);
697
+ if (match === "kind_mismatch") {
698
+ throw new AdcpError("INVALID_REQUEST", {
699
+ message: "get_products returned divergent legacy and canonical format declarations.",
700
+ field,
701
+ suggestion: "Make format_ids and format_options.v1_format_ref describe the same canonical format."
702
+ });
703
+ }
704
+ if (match === "params_mismatch") {
705
+ throw new AdcpError("INVALID_REQUEST", {
706
+ message: "get_products returned a canonical declaration that does not narrow its v1_format_ref.",
707
+ field: `${field}.params`,
708
+ suggestion: "Align canonical format parameters with the referenced legacy format requirements."
709
+ });
710
+ }
711
+ }
712
+ function validateDualProductFormatDeclarations(value, legacyFormatConverter) {
713
+ const seen = /* @__PURE__ */ new WeakSet();
714
+ const visit = (current, path) => {
715
+ if (current === null || typeof current !== "object" || seen.has(current)) return;
716
+ seen.add(current);
717
+ if (Array.isArray(current)) {
718
+ const entries = ownArrayDataValues(current);
719
+ for (let index = 0; index < entries.length; index++) visit(entries[index], `${path}[${index}]`);
720
+ return;
721
+ }
722
+ const record = current;
723
+ const formatOptions = Array.isArray(record.format_options) ? record.format_options : void 0;
724
+ const ownerId = typeof record.product_id === "string" ? record.product_id : typeof record.placement_id === "string" ? record.placement_id : void 0;
725
+ if (formatOptions && ownerId !== void 0) {
726
+ const declarationRefs = [];
727
+ const declarations = ownArrayDataValues(formatOptions);
728
+ for (let index = 0; index < declarations.length; index++) {
729
+ const value2 = declarations[index];
730
+ if (value2 === null || typeof value2 !== "object" || Array.isArray(value2)) continue;
731
+ const declaration = value2;
732
+ const refs = ownArrayDataValues(
733
+ legacyFormatRefsForDeclaration(declaration)
734
+ );
735
+ const field = `${path}.format_options[${index}]`;
736
+ if (declaration.canonical_formats_only === true && refs.length > 0) {
737
+ throw new AdcpError("INVALID_REQUEST", {
738
+ message: "get_products returned canonical_formats_only together with v1_format_ref.",
739
+ field,
740
+ suggestion: "Remove v1_format_ref or set canonical_formats_only to false."
741
+ });
742
+ }
743
+ declarationRefs.push(...refs);
744
+ for (const ref of refs) {
745
+ validateLegacyRefNarrowing(declaration, ref, ownerId, field, legacyFormatConverter);
746
+ }
747
+ }
748
+ if (Array.isArray(record.format_ids)) {
749
+ const formatIds = ownArrayDataValues(record.format_ids);
750
+ const formatIdKeys = /* @__PURE__ */ new Set();
751
+ for (const ref of formatIds) {
752
+ const key = legacyFormatRefKey(ref);
753
+ if (key !== void 0) formatIdKeys.add(key);
754
+ }
755
+ const declarationRefKeys = /* @__PURE__ */ new Set();
756
+ for (const ref of declarationRefs) {
757
+ const key = legacyFormatRefKey(ref);
758
+ if (key !== void 0) declarationRefKeys.add(key);
759
+ }
760
+ let divergent = false;
761
+ for (const key of declarationRefKeys) {
762
+ if (!formatIdKeys.has(key)) divergent = true;
763
+ }
764
+ for (const ref of formatIds) {
765
+ const key = legacyFormatRefKey(ref);
766
+ if (key !== void 0 && declarationRefKeys.has(key)) continue;
767
+ let matched = false;
768
+ for (const value2 of declarations) {
769
+ if (value2 !== null && typeof value2 === "object" && !Array.isArray(value2) && value2.canonical_formats_only !== true && matchLegacyRefToDeclaration(value2, ref, ownerId, legacyFormatConverter) === "match") {
770
+ matched = true;
771
+ break;
772
+ }
773
+ }
774
+ if (!matched) divergent = true;
775
+ }
776
+ if (divergent) {
777
+ throw new AdcpError("INVALID_REQUEST", {
778
+ message: "get_products returned divergent format_ids and format_options.v1_format_ref values.",
779
+ field: path,
780
+ suggestion: "Dual-emitted product declarations must carry the same legacy format references."
781
+ });
782
+ }
783
+ }
784
+ }
785
+ for (const [key, child] of Object.entries(record)) {
786
+ if (key === "format_options") continue;
787
+ visit(child, `${path}.${key}`);
788
+ }
789
+ };
790
+ visit(value, "$");
791
+ }
792
+ function attachCanonicalFormatWireRefs(value, canonicalFormatLegacyResolver, operation, emitTransitionalLegacyRefs = false) {
526
793
  const visit = (current) => {
527
- if (Array.isArray(current)) return current.map(visit);
794
+ if (Array.isArray(current)) {
795
+ const mapped = [];
796
+ for (const entry of ownArrayDataValues(current)) mapped.push(visit(entry));
797
+ return mapped;
798
+ }
528
799
  if (current === null || typeof current !== "object") return current;
529
800
  const record = current;
530
801
  const hasFormatDeclarations = Array.isArray(record.format_options);
531
802
  const declarationOwner = typeof record.product_id === "string" ? record.product_id : typeof record.placement_id === "string" ? record.placement_id : "(response)";
532
803
  let projected = record;
533
804
  if (hasFormatDeclarations) {
534
- const formatOptions = record.format_options.map((value2, index) => {
535
- if (value2 === null || typeof value2 !== "object" || Array.isArray(value2)) return value2;
805
+ const wireLegacyRefs = [];
806
+ const wireLegacyRefKeys = /* @__PURE__ */ new Set();
807
+ const appendWireLegacyRefs = (refs) => {
808
+ for (const ref of refs) {
809
+ const key = legacyFormatRefKey(ref);
810
+ if (key === void 0 || wireLegacyRefKeys.has(key)) continue;
811
+ wireLegacyRefKeys.add(key);
812
+ wireLegacyRefs.push(cloneWireData(ref));
813
+ }
814
+ };
815
+ if (emitTransitionalLegacyRefs) {
816
+ const authoredRefs = authoredFormatIdsByOwner.get(record);
817
+ if (authoredRefs) appendWireLegacyRefs(authoredRefs);
818
+ }
819
+ const formatOptions = [];
820
+ const declarations = ownArrayDataValues(record.format_options);
821
+ for (let index = 0; index < declarations.length; index++) {
822
+ const value2 = declarations[index];
823
+ if (value2 === null || typeof value2 !== "object" || Array.isArray(value2)) {
824
+ formatOptions.push(value2);
825
+ continue;
826
+ }
536
827
  const declaration = value2;
537
- if (declaration.format_kind !== "custom" || declaration.canonical_formats_only === true) return declaration;
538
- let refs = [...legacyFormatRefsForDeclaration(declaration)];
828
+ if (declaration.canonical_formats_only === true) {
829
+ formatOptions.push(declaration);
830
+ continue;
831
+ }
832
+ let refs = ownArrayDataValues(
833
+ legacyFormatRefsForDeclaration(declaration)
834
+ );
835
+ if (emitTransitionalLegacyRefs && refs.length > 0) {
836
+ const clonedRefs2 = refs.map((ref) => cloneWireData(ref));
837
+ appendWireLegacyRefs(clonedRefs2);
838
+ formatOptions.push({ ...declaration, v1_format_ref: clonedRefs2 });
839
+ continue;
840
+ }
841
+ if (declaration.format_kind !== "custom") {
842
+ formatOptions.push(declaration);
843
+ continue;
844
+ }
539
845
  if (refs.length === 0) {
540
846
  try {
541
- refs = resolveCanonicalFormatLegacyRefs(canonicalFormatLegacyResolver, {
847
+ const resolved = resolveCanonicalFormatLegacyRefs(canonicalFormatLegacyResolver, {
542
848
  source: "product",
543
849
  declaration,
544
850
  productId: declarationOwner,
545
851
  field: `${declarationOwner}.format_options[${index}]`
546
- }) ?? [];
852
+ });
853
+ refs = resolved ? ownArrayDataValues(resolved) : [];
547
854
  } catch (error) {
548
855
  if (!(error instanceof CanonicalFormatLegacyResolutionError)) throw error;
549
856
  throw new CreativeFormatProjectionError(
@@ -560,9 +867,15 @@ function attachCanonicalCustomFormatWireRefs(value, canonicalFormatLegacyResolve
560
867
  "custom canonical declaration must set canonical_formats_only or provide an explicit legacy mapping"
561
868
  );
562
869
  }
563
- return { ...declaration, v1_format_ref: refs.map((ref) => ({ ...ref })) };
564
- });
565
- projected = { ...record, format_options: formatOptions };
870
+ const clonedRefs = refs.map((ref) => cloneWireData(ref));
871
+ if (emitTransitionalLegacyRefs) appendWireLegacyRefs(clonedRefs);
872
+ formatOptions.push({ ...declaration, v1_format_ref: clonedRefs });
873
+ }
874
+ projected = {
875
+ ...record,
876
+ format_options: formatOptions,
877
+ ...emitTransitionalLegacyRefs && wireLegacyRefs.length > 0 && { format_ids: wireLegacyRefs }
878
+ };
566
879
  }
567
880
  let changed = projected !== record;
568
881
  const next = { ...projected };
@@ -578,9 +891,18 @@ function attachCanonicalCustomFormatWireRefs(value, canonicalFormatLegacyResolve
578
891
  };
579
892
  return visit(value);
580
893
  }
581
- function asProductResponseForWire(canonicalResponse, wireMode, canonicalFormatLegacyResolver) {
894
+ function asProductResponseForWire(canonicalResponse, wireMode, canonicalFormatLegacyResolver, emitTransitionalLegacyRefs = false) {
582
895
  if (wireMode === "canonical") {
583
- return attachCanonicalCustomFormatWireRefs(canonicalResponse, canonicalFormatLegacyResolver, "get_products");
896
+ const responseForWire = emitTransitionalLegacyRefs && canonicalResponse !== null && typeof canonicalResponse === "object" ? removeResolvedLegacyDropErrors(
897
+ canonicalResponse,
898
+ resolvedLegacyDropDiagnosticsByResponse.get(canonicalResponse) ?? []
899
+ ) : canonicalResponse;
900
+ return attachCanonicalFormatWireRefs(
901
+ responseForWire,
902
+ canonicalFormatLegacyResolver,
903
+ "get_products",
904
+ emitTransitionalLegacyRefs
905
+ );
584
906
  }
585
907
  if (!Array.isArray(canonicalResponse.products)) return canonicalResponse;
586
908
  const products = canonicalResponse.products.map((product) => {
@@ -2492,7 +2814,8 @@ function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observab
2492
2814
  return asProductResponseForWire(
2493
2815
  canonicalResult,
2494
2816
  responseWireMode,
2495
- canonicalFormatLegacyResolver
2817
+ canonicalFormatLegacyResolver,
2818
+ shouldEmitTransitionalDualCreativeWire(ctx.servedAdcpVersion, params)
2496
2819
  );
2497
2820
  };
2498
2821
  const isHandoff = isTaskHandoff(result);
@@ -2714,46 +3037,45 @@ function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observab
2714
3037
  );
2715
3038
  }
2716
3039
  },
2717
- syncCreatives: async (params, ctx) => {
2718
- const responseWireMode = creativeWireModeForRequest(ctx, creativeWireMode, params);
2719
- params = asCanonicalSemanticServerRequest(params, "sync_creatives", legacyFormatConverter);
2720
- const reqCtx = ctxFor(ctx, params);
2721
- const creatives = params.creatives ?? [];
2722
- if (!sales?.syncCreatives) {
2723
- return adcpError("UNSUPPORTED_FEATURE", {
2724
- message: "sync_creatives not supported by this sales platform"
2725
- });
3040
+ ...sales?.syncCreatives && {
3041
+ syncCreatives: async (params, ctx) => {
3042
+ const responseWireMode = creativeWireModeForRequest(ctx, creativeWireMode, params);
3043
+ params = asCanonicalSemanticServerRequest(params, "sync_creatives", legacyFormatConverter);
3044
+ const reqCtx = ctxFor(ctx, params);
3045
+ const creatives = params.creatives ?? [];
3046
+ return projectSync(
3047
+ async () => {
3048
+ const push = extractPushConfig(params, logger, {
3049
+ allowPrivateWebhookUrls: pushOpts.allowPrivateWebhookUrls
3050
+ });
3051
+ const result = await sales.syncCreatives(creatives, reqCtx);
3052
+ return routeIfHandoff(
3053
+ taskRegistry,
3054
+ {
3055
+ tool: "sync_creatives",
3056
+ accountId: reqCtx.account.id,
3057
+ ownerScope: taskOwnerScopeFor(ctx, reqCtx.account.id),
3058
+ pushNotificationUrl: push.url,
3059
+ pushNotificationToken: push.token,
3060
+ pushNotificationOperationId: push.operationId,
3061
+ emitWebhook: taskWebhookEmit ?? ctx.emitWebhook,
3062
+ autoEmitCompletion: pushOpts.autoEmitCompletionWebhooks,
3063
+ observability,
3064
+ logger
3065
+ },
3066
+ result,
3067
+ (rows) => asSemanticServerResponseForWire(
3068
+ { creatives: rows.map(normalizeRowErrors) },
3069
+ "sync_creatives",
3070
+ legacyFormatConverter,
3071
+ canonicalFormatLegacyResolver,
3072
+ responseWireMode
3073
+ )
3074
+ );
3075
+ },
3076
+ (r) => r
3077
+ );
2726
3078
  }
2727
- return projectSync(
2728
- async () => {
2729
- const push = extractPushConfig(params, logger, { allowPrivateWebhookUrls: pushOpts.allowPrivateWebhookUrls });
2730
- const result = await sales.syncCreatives(creatives, reqCtx);
2731
- return routeIfHandoff(
2732
- taskRegistry,
2733
- {
2734
- tool: "sync_creatives",
2735
- accountId: reqCtx.account.id,
2736
- ownerScope: taskOwnerScopeFor(ctx, reqCtx.account.id),
2737
- pushNotificationUrl: push.url,
2738
- pushNotificationToken: push.token,
2739
- pushNotificationOperationId: push.operationId,
2740
- emitWebhook: taskWebhookEmit ?? ctx.emitWebhook,
2741
- autoEmitCompletion: pushOpts.autoEmitCompletionWebhooks,
2742
- observability,
2743
- logger
2744
- },
2745
- result,
2746
- (rows) => asSemanticServerResponseForWire(
2747
- { creatives: rows.map(normalizeRowErrors) },
2748
- "sync_creatives",
2749
- legacyFormatConverter,
2750
- canonicalFormatLegacyResolver,
2751
- responseWireMode
2752
- )
2753
- );
2754
- },
2755
- (r) => r
2756
- );
2757
3079
  },
2758
3080
  ...sales?.getMediaBuyDelivery && {
2759
3081
  getMediaBuyDelivery: async (params, ctx) => {
@@ -2836,7 +3158,7 @@ function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observab
2836
3158
  "media_buy_id",
2837
3159
  logger
2838
3160
  );
2839
- const wireResult = responseWireMode === "canonical" ? attachCanonicalCustomFormatWireRefs(canonicalResult, canonicalFormatLegacyResolver, "get_media_buys") : asSemanticServerResponseForWire(
3161
+ const wireResult = responseWireMode === "canonical" ? attachCanonicalFormatWireRefs(canonicalResult, canonicalFormatLegacyResolver, "get_media_buys") : asSemanticServerResponseForWire(
2840
3162
  result,
2841
3163
  "get_media_buys",
2842
3164
  legacyFormatConverter,
@@ -2889,7 +3211,8 @@ function buildMediaBuyHandlers(platform, taskRegistry, taskWebhookEmit, observab
2889
3211
  const responseWireMode = creativeWireModeForRequest(ctx, creativeWireMode, params);
2890
3212
  const canonicalParams = asCanonicalListCreativesRequest(
2891
3213
  params,
2892
- legacyFormatConverter
3214
+ legacyFormatConverter,
3215
+ responseWireMode
2893
3216
  );
2894
3217
  const reqCtx = ctxFor(ctx, canonicalParams);
2895
3218
  return projectSync(
@@ -3022,7 +3345,8 @@ function buildCreativeHandlers(platform, taskRegistry, taskWebhookEmit, observab
3022
3345
  const responseWireMode = creativeWireModeForRequest(ctx, creativeWireMode, params);
3023
3346
  const canonicalParams = asCanonicalListCreativesRequest(
3024
3347
  params,
3025
- legacyFormatConverter
3348
+ legacyFormatConverter,
3349
+ responseWireMode
3026
3350
  );
3027
3351
  const reqCtx = ctxFor(ctx, canonicalParams);
3028
3352
  return projectSync(