@adcp/sdk 15.9.4 → 15.9.6

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 (71) hide show
  1. package/dist/lib/core/AgentClient.d.mts +4 -2
  2. package/dist/lib/core/AgentClient.d.ts +4 -2
  3. package/dist/lib/core/AgentClient.js +4 -2
  4. package/dist/lib/core/AgentClient.mjs +4 -2
  5. package/dist/lib/core/SingleAgentClient.d.mts +7 -3
  6. package/dist/lib/core/SingleAgentClient.d.ts +7 -3
  7. package/dist/lib/core/SingleAgentClient.js +204 -13
  8. package/dist/lib/core/SingleAgentClient.mjs +209 -13
  9. package/dist/lib/core/TaskExecutor.d.mts +1 -0
  10. package/dist/lib/core/TaskExecutor.d.ts +1 -0
  11. package/dist/lib/core/TaskExecutor.js +48 -34
  12. package/dist/lib/core/TaskExecutor.mjs +49 -35
  13. package/dist/lib/core/product-cache.d.mts +3 -1
  14. package/dist/lib/core/product-cache.d.ts +3 -1
  15. package/dist/lib/core/product-cache.js +2 -1
  16. package/dist/lib/core/product-cache.mjs +2 -1
  17. package/dist/lib/registry/types.d.mts +3 -2
  18. package/dist/lib/registry/types.d.ts +3 -2
  19. package/dist/lib/registry/types.generated.d.mts +21 -9
  20. package/dist/lib/registry/types.generated.d.ts +21 -9
  21. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  22. package/dist/lib/server/decisioning/runtime/from-platform.js +6 -2
  23. package/dist/lib/server/decisioning/runtime/from-platform.mjs +6 -2
  24. package/dist/lib/testing/client.js +3 -5
  25. package/dist/lib/testing/client.mjs +4 -6
  26. package/dist/lib/testing/compliance/comply.js +7 -2
  27. package/dist/lib/testing/compliance/comply.mjs +8 -3
  28. package/dist/lib/testing/index.d.mts +1 -1
  29. package/dist/lib/testing/index.d.ts +1 -1
  30. package/dist/lib/testing/index.js +4 -2
  31. package/dist/lib/testing/index.mjs +8 -2
  32. package/dist/lib/testing/storyboard/runner.js +12 -12
  33. package/dist/lib/testing/storyboard/runner.mjs +14 -13
  34. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  35. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  36. package/dist/lib/testing/storyboard/validations.js +23 -1
  37. package/dist/lib/testing/storyboard/validations.mjs +28 -2
  38. package/dist/lib/testing/types.d.mts +39 -0
  39. package/dist/lib/testing/types.d.ts +39 -0
  40. package/dist/lib/v2/projection/augment-response.js +6 -3
  41. package/dist/lib/v2/projection/augment-response.mjs +8 -5
  42. package/dist/lib/v2/projection/creative-delivery.d.mts +2 -0
  43. package/dist/lib/v2/projection/creative-delivery.d.ts +2 -0
  44. package/dist/lib/v2/projection/creative-delivery.js +36 -17
  45. package/dist/lib/v2/projection/creative-delivery.mjs +37 -18
  46. package/dist/lib/v2/projection/legacy-metadata.d.mts +12 -0
  47. package/dist/lib/v2/projection/legacy-metadata.d.ts +12 -0
  48. package/dist/lib/v2/projection/legacy-metadata.js +41 -1
  49. package/dist/lib/v2/projection/legacy-metadata.mjs +37 -1
  50. package/dist/lib/v2/projection/v1-to-v2.d.mts +7 -0
  51. package/dist/lib/v2/projection/v1-to-v2.d.ts +7 -0
  52. package/dist/lib/v2/projection/v1-to-v2.js +83 -2
  53. package/dist/lib/v2/projection/v1-to-v2.mjs +86 -1
  54. package/dist/lib/validation/index.d.mts +1 -1
  55. package/dist/lib/validation/index.d.ts +1 -1
  56. package/dist/lib/validation/index.js +4 -2
  57. package/dist/lib/validation/index.mjs +4 -2
  58. package/dist/lib/validation/schema-loader.d.mts +26 -0
  59. package/dist/lib/validation/schema-loader.d.ts +26 -0
  60. package/dist/lib/validation/schema-loader.js +98 -14
  61. package/dist/lib/validation/schema-loader.mjs +95 -13
  62. package/dist/lib/version.d.mts +3 -3
  63. package/dist/lib/version.d.ts +3 -3
  64. package/dist/lib/version.js +3 -3
  65. package/dist/lib/version.mjs +3 -3
  66. package/docs/TYPE-SUMMARY.md +2 -2
  67. package/docs/guides/CREATIVE-DELIVERY.md +45 -6
  68. package/docs/guides/VALIDATE-YOUR-AGENT.md +34 -0
  69. package/docs/llms.txt +2 -2
  70. package/docs/migration-14.x-rc-worksheet.md +4 -4
  71. package/package.json +1 -1
@@ -159,6 +159,7 @@ import { resolvePropertyList } from "../server/targeting-helpers.mjs";
159
159
  import {
160
160
  CreativeFormatCapabilityError,
161
161
  CreativeFormatProjectionError,
162
+ canUseProductOptionsForCreativeWire,
162
163
  projectMediaBuyCreativesForDelivery,
163
164
  projectCreativeForDelivery,
164
165
  projectSyncCreativesForDelivery,
@@ -176,6 +177,10 @@ import {
176
177
  import { toCanonicalOnlyResponse } from "../v2/projection/augment-response.mjs";
177
178
  import {
178
179
  legacyFormatRefsForDeclaration,
180
+ isSynthesizedFormatOption,
181
+ selectedFormatOptions,
182
+ rememberSellerFormatEvidence,
183
+ sellerFormatEvidence,
179
184
  rememberSellerDeclaredFormatOption,
180
185
  sellerDeclaredFormatOption,
181
186
  structuredCloneWithLegacyCreativeMetadata
@@ -299,6 +304,56 @@ const RECORDLESS_HMAC_READ_ONLY_TASKS = /* @__PURE__ */ new Set([
299
304
  ]);
300
305
  const TASK_SCOPED_STATE_LIMIT = 1e4;
301
306
  const WEBHOOK_JWKS_RESOLVER_LIMIT = 1024;
307
+ function creativeMatchesProductOption(creative, declaration) {
308
+ const params = isPlainRecord(declaration.params) ? declaration.params : {};
309
+ const assets = isPlainRecord(creative.assets) ? creative.assets : {};
310
+ const slots = Array.isArray(params.slots) ? params.slots : [];
311
+ if (!slots.every((slot) => {
312
+ if (!isPlainRecord(slot) || typeof slot.asset_group_id !== "string") return false;
313
+ const value = assets[slot.asset_group_id];
314
+ const entries = value === void 0 ? [] : Array.isArray(value) ? value : [value];
315
+ const minimum = Math.max(typeof slot.min === "number" ? slot.min : 0, slot.required === true ? 1 : 0);
316
+ if (entries.length < minimum || typeof slot.max === "number" && entries.length > slot.max) return false;
317
+ return entries.every(
318
+ (asset) => isPlainRecord(asset) && (typeof slot.asset_type !== "string" || asset.asset_type === slot.asset_type)
319
+ );
320
+ }))
321
+ return false;
322
+ const formatParams = isPlainRecord(creative.format_parameters) ? creative.format_parameters : {};
323
+ const kind = String(creative.format_kind);
324
+ const mediaType = kind.startsWith("image") ? "image" : kind.startsWith("video") ? "video" : kind.startsWith("audio") ? "audio" : kind === "html5" ? "html" : void 0;
325
+ const assetTypes = mediaType === "video" ? ["video", "vast"] : mediaType === "audio" ? ["audio", "vast", "daast"] : mediaType === "html" ? ["html", "zip"] : mediaType ? [mediaType] : ["image", "video", "audio", "html", "zip", "vast", "daast"];
326
+ const requiredMediaSlots = slots.filter(
327
+ (slot) => isPlainRecord(slot) && (slot.required === true || typeof slot.min === "number" && slot.min > 0) && assetTypes.includes(String(slot.asset_type))
328
+ );
329
+ const preferredGroups = mediaType ? [`${mediaType}_main`, mediaType, kind, ...assetTypes] : [];
330
+ const primaryGroup = (requiredMediaSlots.length === 1 ? String(requiredMediaSlots[0].asset_group_id) : void 0) ?? preferredGroups.find((group) => assets[group] !== void 0);
331
+ const sourceAssets = primaryGroup ? [assets[primaryGroup]] : Object.values(assets);
332
+ const dimensionalAssets = sourceAssets.flatMap((value) => Array.isArray(value) ? value : [value]).filter(
333
+ (value) => isPlainRecord(value) && assetTypes.includes(String(value.asset_type))
334
+ );
335
+ return (dimensionalAssets.length > 0 ? dimensionalAssets : [{}]).every((asset) => {
336
+ const actual = { ...asset, ...formatParams };
337
+ for (const key of ["width", "height"]) {
338
+ if (typeof params[key] === "number" && actual[key] !== params[key]) return false;
339
+ const min = params[`min_${key}`];
340
+ const max = params[`max_${key}`];
341
+ if (typeof min === "number" && (typeof actual[key] !== "number" || actual[key] < min)) return false;
342
+ if (typeof max === "number" && (typeof actual[key] !== "number" || actual[key] > max)) return false;
343
+ }
344
+ if (Array.isArray(params.sizes) && !params.sizes.some((size) => isPlainRecord(size) && size.width === actual.width && size.height === actual.height))
345
+ return false;
346
+ const duration = actual.duration_ms ?? actual.duration_ms_exact;
347
+ const exact = params.duration_ms_exact ?? params.duration_ms;
348
+ if (typeof exact === "number" && duration !== exact) return false;
349
+ if (typeof exact !== "number" && Array.isArray(params.duration_ms_range)) {
350
+ const [min, max] = params.duration_ms_range;
351
+ if (typeof duration !== "number" || typeof min === "number" && duration < min || typeof max === "number" && duration > max)
352
+ return false;
353
+ }
354
+ return true;
355
+ });
356
+ }
302
357
  function isDeferredClientFinalizationContext(value) {
303
358
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
304
359
  const context = value;
@@ -1431,7 +1486,7 @@ class SingleAgentClient {
1431
1486
  }
1432
1487
  }
1433
1488
  }
1434
- rememberCanonicalProductRoutes(products, account, authoritativeProducts) {
1489
+ rememberCanonicalProductRoutes(products, account, authoritativeProducts, evidence) {
1435
1490
  this.invalidateCanonicalProductRoutes(authoritativeProducts ?? products, account);
1436
1491
  const accountScope = this.canonicalAccountScope(account);
1437
1492
  const declaredOptions = authoritativeProducts ? this.sellerDeclaredFormatOptions(authoritativeProducts) : void 0;
@@ -1451,12 +1506,15 @@ class SingleAgentClient {
1451
1506
  const refs = legacyFormatRefsForDeclaration(option);
1452
1507
  const declared = declaredOptions ? declaredOptions.get(product.product_id)?.some((declaredRef) => this.sameCanonicalOptionRef(declaredRef, ref)) : sellerDeclaredFormatOption(option);
1453
1508
  if (declared !== void 0) rememberSellerDeclaredFormatOption(option, declared);
1509
+ if (declared === true && evidence)
1510
+ rememberSellerFormatEvidence(option, { ...evidence, productId: product.product_id });
1454
1511
  this.rememberCanonicalLegacyRoute(this.canonicalLegacyRouteKey(account, product.product_id, ref), {
1455
1512
  kind: "product",
1456
1513
  accountScope,
1457
1514
  productId: product.product_id,
1458
1515
  optionRef: ref,
1459
1516
  refs,
1517
+ ...declared === true && sellerFormatEvidence(option) ? { declaration: structuredCloneWithLegacyCreativeMetadata(option) } : {},
1460
1518
  ...declared === false ? { undeclaredSelector: undeclaredFormatOptionSelector(option) } : {}
1461
1519
  });
1462
1520
  }
@@ -4666,6 +4724,13 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
4666
4724
  }
4667
4725
  }
4668
4726
  const conditionalSnapshot = skipCacheRead ? void 0 : cache?.read(cacheScope, cacheParams, true);
4727
+ const productEvidence = this.usesScopedDiscoveryTransport(
4728
+ effectiveOptions
4729
+ ) ? void 0 : {
4730
+ scopeKey: this.getCapabilityEvidenceScope().scopeKey,
4731
+ accountScope: this.canonicalAccountScope(account),
4732
+ expiresAt: Date.now() + resolveAgentDiscoveryTtlMs(this.config.discoveryCache?.ttlMs)
4733
+ };
4669
4734
  const result = await this.executeAndHandle(
4670
4735
  "get_products",
4671
4736
  "onGetProductsStatusChange",
@@ -4678,7 +4743,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
4678
4743
  projectionCatalogs: projectionCatalogs ?? this.config.projectionCatalogs
4679
4744
  });
4680
4745
  const authoritativeProducts = Array.isArray(data.products) ? data.products : response.products;
4681
- this.rememberCanonicalProductRoutes(response.products, account, authoritativeProducts);
4746
+ this.rememberCanonicalProductRoutes(response.products, account, authoritativeProducts, productEvidence);
4682
4747
  const { _message: _dropLegacyMessage, ...canonical } = response;
4683
4748
  void _dropLegacyMessage;
4684
4749
  return { ...canonical, projection: { diagnostics } };
@@ -4689,7 +4754,9 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
4689
4754
  );
4690
4755
  const finishCache = async (completed) => {
4691
4756
  if (!cache) return completed;
4692
- const cachedResult = cache.write(cacheScope, cacheParams, completed, conditionalSnapshot);
4757
+ const cachedResult = cache.write(cacheScope, cacheParams, completed, conditionalSnapshot, (materialized) => {
4758
+ this.rememberCanonicalProductRoutes(materialized.data?.products ?? [], account, void 0, productEvidence);
4759
+ });
4693
4760
  if (cachedResult.success && cachedResult.status === "completed") {
4694
4761
  if (!skipCacheRead && cacheParams.if_wholesale_feed_version === void 0 && cacheParams.if_pricing_version === void 0 && cachedResult.data.unchanged === true) {
4695
4762
  return this.getProducts(params, inputHandler, {
@@ -4849,7 +4916,12 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
4849
4916
  if (hasCreativeFormatData) {
4850
4917
  this.validateBeforeCreativeCapabilityProbe("create_media_buy", params, taskOptions);
4851
4918
  }
4852
- const wireMode = hasCreativeFormatData ? await this.resolveCreativeFormatWireMode("create_media_buy", taskOptions) : "canonical";
4919
+ const wireMode = hasCreativeFormatData ? await this.resolveCreativeWireMode(
4920
+ "create_media_buy",
4921
+ taskOptions,
4922
+ true,
4923
+ () => this.productOptionsSupportCanonicalWire("create_media_buy", params, [], options)
4924
+ ) : "canonical";
4853
4925
  const wireParams = params;
4854
4926
  const result = await this.executeAndHandle(
4855
4927
  "create_media_buy",
@@ -4937,7 +5009,12 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
4937
5009
  if (hasCreativeFormatData) {
4938
5010
  this.validateBeforeCreativeCapabilityProbe("update_media_buy", params, taskOptions);
4939
5011
  }
4940
- const wireMode = hasCreativeFormatData ? await this.resolveCreativeFormatWireMode("update_media_buy", taskOptions) : "canonical";
5012
+ const wireMode = hasCreativeFormatData ? await this.resolveCreativeWireMode(
5013
+ "update_media_buy",
5014
+ taskOptions,
5015
+ true,
5016
+ () => this.productOptionsSupportCanonicalWire("update_media_buy", params, [], options)
5017
+ ) : "canonical";
4941
5018
  const result = await this.executeAndHandle(
4942
5019
  "update_media_buy",
4943
5020
  "onUpdateMediaBuyStatusChange",
@@ -5026,7 +5103,6 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5026
5103
  projectionCatalogs ?? this.config.projectionCatalogs
5027
5104
  );
5028
5105
  this.validateBeforeCreativeCapabilityProbe("sync_creatives", params, taskOptions);
5029
- const wireMode = await this.resolveCreativeFormatWireMode("sync_creatives", taskOptions);
5030
5106
  const configuredSelectorContainers = [
5031
5107
  ...creativeFormatProjection?.selectorContainers ?? []
5032
5108
  ];
@@ -5038,10 +5114,17 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5038
5114
  const assignmentPackageContainers = Array.isArray(params.assignments) ? params.assignments.flatMap(
5039
5115
  (assignment) => typeof assignment.package_id === "string" && !configuredPackageIds.has(assignment.package_id) ? [{ package_id: assignment.package_id }] : []
5040
5116
  ) : [];
5117
+ const selectorContainers = [...configuredSelectorContainers, ...assignmentPackageContainers];
5118
+ const wireMode = await this.resolveCreativeWireMode(
5119
+ "sync_creatives",
5120
+ taskOptions,
5121
+ true,
5122
+ () => this.productOptionsSupportCanonicalWire("sync_creatives", params, selectorContainers, options)
5123
+ );
5041
5124
  const wireParams = this.projectUndeclaredFormatOptions(
5042
5125
  projectSyncCreativesForDelivery(
5043
5126
  params,
5044
- [...configuredSelectorContainers, ...assignmentPackageContainers],
5127
+ selectorContainers,
5045
5128
  wireMode,
5046
5129
  effectiveLegacyFormatConverter,
5047
5130
  effectiveCanonicalFormatLegacyResolver
@@ -5081,16 +5164,17 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5081
5164
  * sending a mutation. Discovers capabilities and the current tool's input
5082
5165
  * schema, or reuses fresh cached/primed evidence, with this client's wire pin.
5083
5166
  *
5084
- * `unknown` preserves the conservative legacy projection for older peers
5085
- * whose capabilities and schema do not establish support. Projection still
5086
- * requires an unambiguous legacy reference for each creative in that case.
5167
+ * This context-free check returns `unknown` when capabilities and schema do
5168
+ * not establish support. A write to a proven 3.1 seller can still select
5169
+ * canonical delivery from matching seller-advertised product options;
5170
+ * otherwise projection requires an unambiguous legacy reference.
5087
5171
  * Conflicting evidence and unproven support with a 3.2+ buyer pin throw
5088
5172
  * `CreativeFormatCapabilityError`, just as they do during a write.
5089
5173
  */
5090
5174
  async resolveCreativeFormatWireMode(taskType, options) {
5091
5175
  return this.resolveCreativeWireMode(taskType, options, true);
5092
5176
  }
5093
- async resolveCreativeWireMode(taskType, options, canRediscover) {
5177
+ async resolveCreativeWireMode(taskType, options, canRediscover, matchingProductOptions) {
5094
5178
  const discoveryContext = {};
5095
5179
  const capabilities = await this.getCapabilities({
5096
5180
  ...options,
@@ -5109,13 +5193,16 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5109
5193
  `Seller capability and ${taskType} input schema disagree about canonical creative support`
5110
5194
  );
5111
5195
  }
5112
- const resolved = declared !== "unknown" ? declared : schema;
5196
+ let resolved = declared !== "unknown" ? declared : schema;
5197
+ if (resolved === "unknown" && canUseProductOptionsForCreativeWire(capabilities, wireRelease) && matchingProductOptions?.()) {
5198
+ resolved = "canonical";
5199
+ }
5113
5200
  const release = /^v?(\d+)\.(\d+)(?:\.|-|$)/.exec(wireRelease.trim());
5114
5201
  const canonicalRequiredByBuyerPin = release !== null && (Number(release[1]) > 3 || Number(release[1]) === 3 && Number(release[2]) >= 2);
5115
5202
  if (resolved === "unknown" && canonicalRequiredByBuyerPin) {
5116
5203
  if (canRediscover && !usesScopedFetch && this.normalizedAgent.protocol === "mcp" && this.capabilitiesFromSharedCache && toolSchemas?.has(taskType) && !toolInputSchemasByProperties.has(toolSchemas)) {
5117
5204
  await this.invalidateDiscoveryCache(options);
5118
- return this.resolveCreativeWireMode(taskType, options, false);
5205
+ return this.resolveCreativeWireMode(taskType, options, false, matchingProductOptions);
5119
5206
  }
5120
5207
  throw new CreativeFormatCapabilityError(
5121
5208
  `Cannot prove which AdCP release the seller serves for ${taskType}; a 3.2+ client will not guess a legacy creative wire shape`
@@ -5123,6 +5210,115 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5123
5210
  }
5124
5211
  return resolved;
5125
5212
  }
5213
+ usesScopedDiscoveryTransport(options) {
5214
+ return normalizeTransportOptions(options?.transport ?? this.config.transport)?.trustedFetchFn !== void 0;
5215
+ }
5216
+ /** Product evidence applies to this request only; it must never prime a tool-wide capability. */
5217
+ productOptionsSupportCanonicalWire(taskType, request, selectorContainers = [], options) {
5218
+ if (!isPlainRecord(request) || this.usesScopedDiscoveryTransport(options)) return false;
5219
+ const scopeKey = this.getCapabilityEvidenceScope().scopeKey;
5220
+ const accountScope = this.canonicalAccountScope(request.account);
5221
+ const optionRef = (value) => {
5222
+ if (!isPlainRecord(value) || typeof value.format_option_id !== "string" || !value.format_option_id) {
5223
+ return void 0;
5224
+ }
5225
+ if (typeof value.publisher_domain === "string") {
5226
+ return {
5227
+ scope: "publisher",
5228
+ publisher_domain: value.publisher_domain,
5229
+ format_option_id: value.format_option_id
5230
+ };
5231
+ }
5232
+ if (value.scope !== void 0 && value.scope !== "product") return void 0;
5233
+ return { scope: "product", format_option_id: value.format_option_id };
5234
+ };
5235
+ const accepts = (container, creative, depth = 0) => {
5236
+ if (depth > 32) return false;
5237
+ if (Array.isArray(container.selector_containers)) {
5238
+ return container.selector_containers.length > 0 && container.selector_containers.every((nested) => accepts(nested, creative, depth + 1));
5239
+ }
5240
+ const refs = Array.isArray(container.format_option_refs) ? container.format_option_refs.map(optionRef) : [];
5241
+ if (refs.some((ref) => ref === void 0)) return false;
5242
+ const creativeRef = isPlainRecord(creative) ? optionRef(creative.format_option_ref) : void 0;
5243
+ if (isPlainRecord(creative) && creative.format_option_ref !== void 0 && !creativeRef) return false;
5244
+ const isSellerDeclaration = (value) => {
5245
+ if (!isPlainRecord(value) || sellerDeclaredFormatOption(value) !== true || isSynthesizedFormatOption(value)) {
5246
+ return false;
5247
+ }
5248
+ const evidence2 = sellerFormatEvidence(value);
5249
+ if (!evidence2 || evidence2.scopeKey !== scopeKey || typeof container.product_id === "string" && evidence2.productId !== container.product_id || evidence2.expiresAt <= Date.now() || evidence2.accountScope !== accountScope && evidence2.accountScope !== "none")
5250
+ return false;
5251
+ const ref = optionRef(value);
5252
+ if (ref && formatOptionOriginFromRoutes(
5253
+ options?.canonicalFormatLegacyResolver,
5254
+ ref,
5255
+ typeof container.product_id === "string" ? container.product_id : void 0
5256
+ )?.seller_declared === false)
5257
+ return false;
5258
+ return !ref || typeof container.product_id !== "string" || this.cachedFormatOptionOrigin(request.account, ref, container.product_id)?.seller_declared !== false;
5259
+ };
5260
+ const declarations = [
5261
+ ...(Array.isArray(container.format_options) ? container.format_options : []).filter(isSellerDeclaration),
5262
+ ...selectedFormatOptions(container).filter(isSellerDeclaration)
5263
+ ];
5264
+ if (typeof container.product_id === "string") {
5265
+ for (const ref of [...refs, ...creativeRef ? [creativeRef] : []]) {
5266
+ if (!ref) continue;
5267
+ const route = this.routeForOption(request.account, container.product_id, ref);
5268
+ if (route?.kind === "product" && route.declaration && isSellerDeclaration(route.declaration))
5269
+ declarations.push(route.declaration);
5270
+ }
5271
+ }
5272
+ const available = declarations.filter((declaration) => declaration.experimental !== true);
5273
+ const matchesRef = (declaration, ref) => {
5274
+ const declaredRef = optionRef(declaration);
5275
+ return declaredRef !== void 0 && this.sameCanonicalOptionRef(declaredRef, ref);
5276
+ };
5277
+ if (!refs.every((ref) => available.some((declaration) => matchesRef(declaration, ref)))) return false;
5278
+ const selected = available.filter(
5279
+ (declaration) => refs.length === 0 || refs.some((ref) => matchesRef(declaration, ref))
5280
+ );
5281
+ if (creative === void 0) return refs.length > 0 && selected.length > 0;
5282
+ if (!isPlainRecord(creative) || typeof creative.format_kind !== "string" || creative.format_id !== void 0)
5283
+ return false;
5284
+ return selected.some(
5285
+ (declaration) => declaration.format_kind === creative.format_kind && (!creativeRef || matchesRef(declaration, creativeRef)) && creativeMatchesProductOption(creative, declaration)
5286
+ );
5287
+ };
5288
+ if (taskType === "sync_creatives") {
5289
+ if (!Array.isArray(request.creatives) || request.creatives.length === 0) return false;
5290
+ const creatives = request.creatives;
5291
+ const assignments = Array.isArray(request.assignments) ? request.assignments : [];
5292
+ if (!assignments.every(
5293
+ (value) => isPlainRecord(value) && typeof value.package_id === "string" && typeof value.creative_id === "string" && creatives.some((creative) => isPlainRecord(creative) && creative.creative_id === value.creative_id)
5294
+ ))
5295
+ return false;
5296
+ return creatives.every((creative) => {
5297
+ if (!isPlainRecord(creative)) return false;
5298
+ const assigned = new Set(
5299
+ assignments.flatMap(
5300
+ (value) => isPlainRecord(value) && value.creative_id === creative.creative_id && typeof value.package_id === "string" ? [value.package_id] : []
5301
+ )
5302
+ );
5303
+ const relevant = assigned.size === 0 ? selectorContainers : selectorContainers.filter(
5304
+ (container) => typeof container.package_id === "string" && assigned.has(container.package_id)
5305
+ );
5306
+ return relevant.length > 0 && [...assigned].every((id) => relevant.some((container) => container.package_id === id)) && relevant.every((container) => accepts(container, creative));
5307
+ });
5308
+ }
5309
+ const packages = ["packages", "new_packages"].flatMap((key) => Array.isArray(request[key]) ? request[key] : []);
5310
+ let evidence = false;
5311
+ return packages.length > 0 && packages.every((pkg) => {
5312
+ if (!isPlainRecord(pkg)) return false;
5313
+ const creatives = Array.isArray(pkg.creatives) ? pkg.creatives : [];
5314
+ const hasSelectors = Array.isArray(pkg.format_option_refs) && pkg.format_option_refs.length > 0;
5315
+ if (!hasSelectors && creatives.length === 0) {
5316
+ return pkg.format_kind === void 0 && pkg.format_ids === void 0;
5317
+ }
5318
+ evidence = true;
5319
+ return (!hasSelectors || accepts(pkg)) && creatives.every((creative) => accepts(pkg, creative));
5320
+ }) && evidence;
5321
+ }
5126
5322
  /** Validate malformed creative payloads before capability discovery can perform I/O. */
5127
5323
  validateBeforeCreativeCapabilityProbe(taskType, params, options) {
5128
5324
  if (options.skipIdempotencyAutoInject || options.skipAccountValidation || options.skipRequestValidation) return;
@@ -435,6 +435,7 @@ export declare class TaskExecutor {
435
435
  * AJV runs.
436
436
  */
437
437
  private validateResponseSchema;
438
+ private validateResponseSchemaAgainst;
438
439
  private responseAdcpVersionForValidation;
439
440
  private effectiveResponseAdcpVersion;
440
441
  /**
@@ -435,6 +435,7 @@ export declare class TaskExecutor {
435
435
  * AJV runs.
436
436
  */
437
437
  private validateResponseSchema;
438
+ private validateResponseSchemaAgainst;
438
439
  private responseAdcpVersionForValidation;
439
440
  private effectiveResponseAdcpVersion;
440
441
  /**
@@ -4398,41 +4398,22 @@ class TaskExecutor {
4398
4398
  const mode = this.responseValidationMode;
4399
4399
  const logViolations = this.config.logSchemaViolations !== false;
4400
4400
  try {
4401
- let validationVersion = (serverVersion ?? this.lastKnownServerVersion) === "v2" ? "v2.5" : this.responseAdcpVersionForValidation() ?? this.config.adcpVersion ?? import_version.ADCP_VERSION;
4402
- if (taskName === "get_adcp_capabilities" && validationVersion !== "v2.5") {
4403
- validationVersion = (0, import_schema_loader.resolveServedValidationVersion)(response, validationVersion);
4404
- }
4405
- const normalizedResponse = this.normalizeResponseForValidation(response, taskName, validationVersion === "v2.5");
4406
- const outcome = (0, import_client_hooks.validateIncomingResponse)(taskName, normalizedResponse, mode, void 0, validationVersion);
4407
- if (outcome.valid) return { valid: true, errors: [] };
4408
- const errorStrings = outcome.issues.map((i) => `${i.pointer}: ${i.message}`);
4409
- if (logViolations) {
4410
- debugLogs.push({
4411
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4412
- type: mode === "strict" ? "error" : "warning",
4413
- message: `Schema validation ${mode === "strict" ? "failed" : "warning"} for ${taskName}: ${(0, import_schema_validator.formatIssues)(
4414
- outcome.issues
4415
- )}`,
4416
- errors: errorStrings,
4417
- issues: outcome.issues,
4418
- schemaVariant: outcome.variant,
4419
- schemaVersion: validationVersion,
4420
- mode
4421
- });
4422
- }
4423
- if (mode === "warn") {
4424
- if (logViolations) {
4425
- console.warn(
4426
- `Schema validation failed for ${taskName} against AdCP ${validationVersion} (non-blocking):`,
4427
- errorStrings
4428
- );
4429
- }
4430
- return { valid: true, errors: [] };
4431
- }
4432
- if (logViolations) {
4433
- console.error(`Schema validation failed for ${taskName} against AdCP ${validationVersion}:`, errorStrings);
4401
+ const pinnedValidationVersion = (serverVersion ?? this.lastKnownServerVersion) === "v2" ? "v2.5" : this.responseAdcpVersionForValidation() ?? this.config.adcpVersion ?? import_version.ADCP_VERSION;
4402
+ if (taskName === "get_adcp_capabilities" && pinnedValidationVersion !== "v2.5") {
4403
+ return (0, import_schema_loader.withServedValidationVersion)(
4404
+ response,
4405
+ pinnedValidationVersion,
4406
+ (validationVersion) => this.validateResponseSchemaAgainst(response, taskName, debugLogs, mode, logViolations, validationVersion)
4407
+ );
4434
4408
  }
4435
- return { valid: false, errors: errorStrings };
4409
+ return this.validateResponseSchemaAgainst(
4410
+ response,
4411
+ taskName,
4412
+ debugLogs,
4413
+ mode,
4414
+ logViolations,
4415
+ pinnedValidationVersion
4416
+ );
4436
4417
  } catch (error) {
4437
4418
  const errorMessage = error instanceof Error ? error.message : "unknown error";
4438
4419
  console.error("Error during schema validation:", errorMessage);
@@ -4442,6 +4423,39 @@ class TaskExecutor {
4442
4423
  };
4443
4424
  }
4444
4425
  }
4426
+ validateResponseSchemaAgainst(response, taskName, debugLogs, mode, logViolations, validationVersion) {
4427
+ const normalizedResponse = this.normalizeResponseForValidation(response, taskName, validationVersion === "v2.5");
4428
+ const outcome = (0, import_client_hooks.validateIncomingResponse)(taskName, normalizedResponse, mode, void 0, validationVersion);
4429
+ if (outcome.valid) return { valid: true, errors: [] };
4430
+ const errorStrings = outcome.issues.map((i) => `${i.pointer}: ${i.message}`);
4431
+ if (logViolations) {
4432
+ debugLogs.push({
4433
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4434
+ type: mode === "strict" ? "error" : "warning",
4435
+ message: `Schema validation ${mode === "strict" ? "failed" : "warning"} for ${taskName}: ${(0, import_schema_validator.formatIssues)(
4436
+ outcome.issues
4437
+ )}`,
4438
+ errors: errorStrings,
4439
+ issues: outcome.issues,
4440
+ schemaVariant: outcome.variant,
4441
+ schemaVersion: validationVersion,
4442
+ mode
4443
+ });
4444
+ }
4445
+ if (mode === "warn") {
4446
+ if (logViolations) {
4447
+ console.warn(
4448
+ `Schema validation failed for ${taskName} against AdCP ${validationVersion} (non-blocking):`,
4449
+ errorStrings
4450
+ );
4451
+ }
4452
+ return { valid: true, errors: [] };
4453
+ }
4454
+ if (logViolations) {
4455
+ console.error(`Schema validation failed for ${taskName} against AdCP ${validationVersion}:`, errorStrings);
4456
+ }
4457
+ return { valid: false, errors: errorStrings };
4458
+ }
4445
4459
  responseAdcpVersionForValidation() {
4446
4460
  if (this.config.versionEnvelope === "major-only") return "3.0";
4447
4461
  return void 0;
@@ -15,7 +15,7 @@ import {
15
15
  resolveValidationModes
16
16
  } from "../validation/client-hooks.mjs";
17
17
  import { formatIssues } from "../validation/schema-validator.mjs";
18
- import { resolveServedValidationVersion } from "../validation/schema-loader.mjs";
18
+ import { withServedValidationVersion } from "../validation/schema-loader.mjs";
19
19
  import { ADCP_VERSION } from "../version.mjs";
20
20
  import { unwrapProtocolResponse, isAdcpOperationSuccess } from "../utils/response-unwrapper.mjs";
21
21
  import { extractAdcpErrorInfo, extractCorrelationId } from "../utils/error-extraction.mjs";
@@ -4371,41 +4371,22 @@ class TaskExecutor {
4371
4371
  const mode = this.responseValidationMode;
4372
4372
  const logViolations = this.config.logSchemaViolations !== false;
4373
4373
  try {
4374
- let validationVersion = (serverVersion ?? this.lastKnownServerVersion) === "v2" ? "v2.5" : this.responseAdcpVersionForValidation() ?? this.config.adcpVersion ?? ADCP_VERSION;
4375
- if (taskName === "get_adcp_capabilities" && validationVersion !== "v2.5") {
4376
- validationVersion = resolveServedValidationVersion(response, validationVersion);
4377
- }
4378
- const normalizedResponse = this.normalizeResponseForValidation(response, taskName, validationVersion === "v2.5");
4379
- const outcome = validateIncomingResponse(taskName, normalizedResponse, mode, void 0, validationVersion);
4380
- if (outcome.valid) return { valid: true, errors: [] };
4381
- const errorStrings = outcome.issues.map((i) => `${i.pointer}: ${i.message}`);
4382
- if (logViolations) {
4383
- debugLogs.push({
4384
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4385
- type: mode === "strict" ? "error" : "warning",
4386
- message: `Schema validation ${mode === "strict" ? "failed" : "warning"} for ${taskName}: ${formatIssues(
4387
- outcome.issues
4388
- )}`,
4389
- errors: errorStrings,
4390
- issues: outcome.issues,
4391
- schemaVariant: outcome.variant,
4392
- schemaVersion: validationVersion,
4393
- mode
4394
- });
4395
- }
4396
- if (mode === "warn") {
4397
- if (logViolations) {
4398
- console.warn(
4399
- `Schema validation failed for ${taskName} against AdCP ${validationVersion} (non-blocking):`,
4400
- errorStrings
4401
- );
4402
- }
4403
- return { valid: true, errors: [] };
4404
- }
4405
- if (logViolations) {
4406
- console.error(`Schema validation failed for ${taskName} against AdCP ${validationVersion}:`, errorStrings);
4374
+ const pinnedValidationVersion = (serverVersion ?? this.lastKnownServerVersion) === "v2" ? "v2.5" : this.responseAdcpVersionForValidation() ?? this.config.adcpVersion ?? ADCP_VERSION;
4375
+ if (taskName === "get_adcp_capabilities" && pinnedValidationVersion !== "v2.5") {
4376
+ return withServedValidationVersion(
4377
+ response,
4378
+ pinnedValidationVersion,
4379
+ (validationVersion) => this.validateResponseSchemaAgainst(response, taskName, debugLogs, mode, logViolations, validationVersion)
4380
+ );
4407
4381
  }
4408
- return { valid: false, errors: errorStrings };
4382
+ return this.validateResponseSchemaAgainst(
4383
+ response,
4384
+ taskName,
4385
+ debugLogs,
4386
+ mode,
4387
+ logViolations,
4388
+ pinnedValidationVersion
4389
+ );
4409
4390
  } catch (error) {
4410
4391
  const errorMessage = error instanceof Error ? error.message : "unknown error";
4411
4392
  console.error("Error during schema validation:", errorMessage);
@@ -4415,6 +4396,39 @@ class TaskExecutor {
4415
4396
  };
4416
4397
  }
4417
4398
  }
4399
+ validateResponseSchemaAgainst(response, taskName, debugLogs, mode, logViolations, validationVersion) {
4400
+ const normalizedResponse = this.normalizeResponseForValidation(response, taskName, validationVersion === "v2.5");
4401
+ const outcome = validateIncomingResponse(taskName, normalizedResponse, mode, void 0, validationVersion);
4402
+ if (outcome.valid) return { valid: true, errors: [] };
4403
+ const errorStrings = outcome.issues.map((i) => `${i.pointer}: ${i.message}`);
4404
+ if (logViolations) {
4405
+ debugLogs.push({
4406
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4407
+ type: mode === "strict" ? "error" : "warning",
4408
+ message: `Schema validation ${mode === "strict" ? "failed" : "warning"} for ${taskName}: ${formatIssues(
4409
+ outcome.issues
4410
+ )}`,
4411
+ errors: errorStrings,
4412
+ issues: outcome.issues,
4413
+ schemaVariant: outcome.variant,
4414
+ schemaVersion: validationVersion,
4415
+ mode
4416
+ });
4417
+ }
4418
+ if (mode === "warn") {
4419
+ if (logViolations) {
4420
+ console.warn(
4421
+ `Schema validation failed for ${taskName} against AdCP ${validationVersion} (non-blocking):`,
4422
+ errorStrings
4423
+ );
4424
+ }
4425
+ return { valid: true, errors: [] };
4426
+ }
4427
+ if (logViolations) {
4428
+ console.error(`Schema validation failed for ${taskName} against AdCP ${validationVersion}:`, errorStrings);
4429
+ }
4430
+ return { valid: false, errors: errorStrings };
4431
+ }
4418
4432
  responseAdcpVersionForValidation() {
4419
4433
  if (this.config.versionEnvelope === "major-only") return "3.0";
4420
4434
  return void 0;
@@ -12,7 +12,9 @@ export declare class ProductCache {
12
12
  private key;
13
13
  read<T>(scope: string, params: Record<string, unknown>, allowExpired?: boolean): TaskResult<T> | undefined;
14
14
  conditionalParams(scope: string, params: Record<string, unknown>): Record<string, unknown>;
15
- write<T>(scope: string, params: Record<string, unknown>, result: TaskResult<T>, conditionalSnapshot?: TaskResult<T>): TaskResult<T>;
15
+ write<T>(scope: string, params: Record<string, unknown>, result: TaskResult<T>, conditionalSnapshot?: TaskResult<T>,
16
+ /** @internal Refresh private discovery evidence before a confirmed snapshot is cloned into storage. */
17
+ onRevalidated?: (materialized: TaskResult<T>) => void): TaskResult<T>;
16
18
  clear(): void;
17
19
  }
18
20
  export declare function createProductCache(options?: ProductCacheOptions): ProductCache;
@@ -12,7 +12,9 @@ export declare class ProductCache {
12
12
  private key;
13
13
  read<T>(scope: string, params: Record<string, unknown>, allowExpired?: boolean): TaskResult<T> | undefined;
14
14
  conditionalParams(scope: string, params: Record<string, unknown>): Record<string, unknown>;
15
- write<T>(scope: string, params: Record<string, unknown>, result: TaskResult<T>, conditionalSnapshot?: TaskResult<T>): TaskResult<T>;
15
+ write<T>(scope: string, params: Record<string, unknown>, result: TaskResult<T>, conditionalSnapshot?: TaskResult<T>,
16
+ /** @internal Refresh private discovery evidence before a confirmed snapshot is cloned into storage. */
17
+ onRevalidated?: (materialized: TaskResult<T>) => void): TaskResult<T>;
16
18
  clear(): void;
17
19
  }
18
20
  export declare function createProductCache(options?: ProductCacheOptions): ProductCache;
@@ -86,7 +86,7 @@ class ProductCache {
86
86
  ...typeof data.pricing_version === "string" && { if_pricing_version: data.pricing_version }
87
87
  };
88
88
  }
89
- write(scope, params, result, conditionalSnapshot) {
89
+ write(scope, params, result, conditionalSnapshot, onRevalidated) {
90
90
  const key = this.key(scope, params);
91
91
  if (!key) return result;
92
92
  if (!result.success || result.status !== "completed" || !result.data || typeof result.data !== "object")
@@ -109,6 +109,7 @@ class ProductCache {
109
109
  return result;
110
110
  if (params.if_wholesale_feed_version !== void 0 || params.if_pricing_version !== void 0) return result;
111
111
  completed = { ...result, data: (0, import_legacy_metadata.structuredCloneWithLegacyCreativeMetadata)(oldData) };
112
+ onRevalidated?.(completed);
112
113
  } else if (!Array.isArray(data.products)) return result;
113
114
  if (!this.entries.has(key) && this.entries.size >= (this.options.maxEntries ?? 1e3))
114
115
  this.entries.delete(this.entries.keys().next().value);
@@ -62,7 +62,7 @@ class ProductCache {
62
62
  ...typeof data.pricing_version === "string" && { if_pricing_version: data.pricing_version }
63
63
  };
64
64
  }
65
- write(scope, params, result, conditionalSnapshot) {
65
+ write(scope, params, result, conditionalSnapshot, onRevalidated) {
66
66
  const key = this.key(scope, params);
67
67
  if (!key) return result;
68
68
  if (!result.success || result.status !== "completed" || !result.data || typeof result.data !== "object")
@@ -85,6 +85,7 @@ class ProductCache {
85
85
  return result;
86
86
  if (params.if_wholesale_feed_version !== void 0 || params.if_pricing_version !== void 0) return result;
87
87
  completed = { ...result, data: structuredCloneWithLegacyCreativeMetadata(oldData) };
88
+ onRevalidated?.(completed);
88
89
  } else if (!Array.isArray(data.products)) return result;
89
90
  if (!this.entries.has(key) && this.entries.size >= (this.options.maxEntries ?? 1e3))
90
91
  this.entries.delete(this.entries.keys().next().value);
@@ -144,9 +144,10 @@ export type SavePropertyRequest = Omit<RegistrySavePropertyRequest, 'authorized_
144
144
  /** Response from POST /api/properties/save (200) */
145
145
  export type SavePropertyResponse = operations['saveProperty']['responses']['200']['content']['application/json'];
146
146
  type RegistryResolveIdentifiersRequest = NonNullable<operations['resolveIdentifiers']['requestBody']>['content']['application/json'];
147
- /** Request body for POST /api/registry/resolve. `mode` defaults to `resolve` server-side. */
148
- export type ResolveIdentifiersRequest = Omit<RegistryResolveIdentifiersRequest, 'mode'> & {
147
+ /** Request body for POST /api/registry/resolve. Mode and sandbox use the registry's defaults when omitted. */
148
+ export type ResolveIdentifiersRequest = Omit<RegistryResolveIdentifiersRequest, 'mode' | 'sandbox'> & {
149
149
  mode?: RegistryResolveIdentifiersRequest['mode'];
150
+ sandbox?: RegistryResolveIdentifiersRequest['sandbox'];
150
151
  };
151
152
  /** Response from POST /api/registry/resolve (200) */
152
153
  export type ResolveIdentifiersResponse = operations['resolveIdentifiers']['responses']['200']['content']['application/json'];