@adcp/sdk 16.2.0 → 16.4.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 (161) hide show
  1. package/AGENTS.md +2 -0
  2. package/dist/lib/adapters/version/3.1/assignments.d.mts +12 -0
  3. package/dist/lib/adapters/version/3.1/assignments.d.ts +12 -0
  4. package/dist/lib/adapters/version/3.1/assignments.js +67 -0
  5. package/dist/lib/adapters/version/3.1/assignments.mjs +43 -0
  6. package/dist/lib/adapters/version/3.1/bidding.d.mts +3 -0
  7. package/dist/lib/adapters/version/3.1/bidding.d.ts +3 -0
  8. package/dist/lib/adapters/version/3.1/bidding.js +115 -0
  9. package/dist/lib/adapters/version/3.1/bidding.mjs +90 -0
  10. package/dist/lib/adapters/version/3.1/delivery.d.mts +7 -0
  11. package/dist/lib/adapters/version/3.1/delivery.d.ts +7 -0
  12. package/dist/lib/adapters/version/3.1/delivery.js +78 -0
  13. package/dist/lib/adapters/version/3.1/delivery.mjs +54 -0
  14. package/dist/lib/adapters/version/3.1/index.d.mts +3 -0
  15. package/dist/lib/adapters/version/3.1/index.d.ts +3 -0
  16. package/dist/lib/adapters/version/3.1/index.js +36 -0
  17. package/dist/lib/adapters/version/3.1/index.mjs +12 -0
  18. package/dist/lib/adapters/version/index.d.mts +59 -7
  19. package/dist/lib/adapters/version/index.d.ts +59 -7
  20. package/dist/lib/adapters/version/index.js +51 -2
  21. package/dist/lib/adapters/version/index.mjs +49 -1
  22. package/dist/lib/adapters/version/negotiate.d.mts +71 -0
  23. package/dist/lib/adapters/version/negotiate.d.ts +71 -0
  24. package/dist/lib/adapters/version/negotiate.js +101 -0
  25. package/dist/lib/adapters/version/negotiate.mjs +73 -0
  26. package/dist/lib/adapters/version/schema-projection.d.mts +33 -0
  27. package/dist/lib/adapters/version/schema-projection.d.ts +33 -0
  28. package/dist/lib/adapters/version/schema-projection.js +294 -0
  29. package/dist/lib/adapters/version/schema-projection.mjs +269 -0
  30. package/dist/lib/adapters/version/translate.d.mts +122 -0
  31. package/dist/lib/adapters/version/translate.d.ts +122 -0
  32. package/dist/lib/adapters/version/translate.js +169 -0
  33. package/dist/lib/adapters/version/translate.mjs +141 -0
  34. package/dist/lib/adapters/version/types.d.mts +51 -10
  35. package/dist/lib/adapters/version/types.d.ts +51 -10
  36. package/dist/lib/core/AgentClient.d.mts +1 -1
  37. package/dist/lib/core/AgentClient.d.ts +1 -1
  38. package/dist/lib/core/AgentClient.js +17 -1
  39. package/dist/lib/core/AgentClient.mjs +17 -1
  40. package/dist/lib/core/ConversationTypes.d.mts +7 -0
  41. package/dist/lib/core/ConversationTypes.d.ts +7 -0
  42. package/dist/lib/core/SingleAgentClient.d.mts +82 -0
  43. package/dist/lib/core/SingleAgentClient.d.ts +82 -0
  44. package/dist/lib/core/SingleAgentClient.js +336 -15
  45. package/dist/lib/core/SingleAgentClient.mjs +350 -17
  46. package/dist/lib/core/TaskExecutor.d.mts +1 -1
  47. package/dist/lib/core/TaskExecutor.d.ts +1 -1
  48. package/dist/lib/core/TaskExecutor.js +182 -31
  49. package/dist/lib/core/TaskExecutor.mjs +183 -32
  50. package/dist/lib/core/account-reduction.d.mts +36 -0
  51. package/dist/lib/core/account-reduction.d.ts +36 -0
  52. package/dist/lib/core/account-reduction.js +127 -0
  53. package/dist/lib/core/account-reduction.mjs +98 -0
  54. package/dist/lib/core/buyer-account-registry.d.mts +52 -0
  55. package/dist/lib/core/buyer-account-registry.d.ts +52 -0
  56. package/dist/lib/core/buyer-account-registry.js +142 -6
  57. package/dist/lib/core/buyer-account-registry.mjs +142 -6
  58. package/dist/lib/errors/index.d.mts +34 -0
  59. package/dist/lib/errors/index.d.ts +34 -0
  60. package/dist/lib/errors/index.js +11 -0
  61. package/dist/lib/errors/index.mjs +10 -0
  62. package/dist/lib/index.d.mts +1 -1
  63. package/dist/lib/index.d.ts +1 -1
  64. package/dist/lib/index.js +2 -0
  65. package/dist/lib/index.mjs +2 -0
  66. package/dist/lib/media-buy/action-contracts.js +4 -4
  67. package/dist/lib/media-buy/action-contracts.mjs +4 -4
  68. package/dist/lib/media-buy/action-types.d.mts +3 -2
  69. package/dist/lib/media-buy/action-types.d.ts +3 -2
  70. package/dist/lib/media-buy/compatibility.d.mts +1 -0
  71. package/dist/lib/media-buy/compatibility.d.ts +1 -0
  72. package/dist/lib/media-buy/compatibility.js +33 -10
  73. package/dist/lib/media-buy/compatibility.mjs +32 -14
  74. package/dist/lib/media-buy/update-fields.generated.d.mts +3 -2
  75. package/dist/lib/media-buy/update-fields.generated.d.ts +3 -2
  76. package/dist/lib/negotiation/buyer.js +11 -5
  77. package/dist/lib/negotiation/buyer.mjs +11 -5
  78. package/dist/lib/negotiation/types.d.mts +4 -2
  79. package/dist/lib/negotiation/types.d.ts +4 -2
  80. package/dist/lib/negotiation/verification.d.mts +15 -6
  81. package/dist/lib/negotiation/verification.d.ts +15 -6
  82. package/dist/lib/negotiation/verification.js +146 -27
  83. package/dist/lib/negotiation/verification.mjs +146 -28
  84. package/dist/lib/negotiation/version.d.mts +6 -0
  85. package/dist/lib/negotiation/version.d.ts +6 -0
  86. package/dist/lib/negotiation/version.js +60 -0
  87. package/dist/lib/negotiation/version.mjs +33 -0
  88. package/dist/lib/registry/types.generated.d.mts +1 -1
  89. package/dist/lib/registry/types.generated.d.ts +1 -1
  90. package/dist/lib/server/create-adcp-server.d.mts +21 -0
  91. package/dist/lib/server/create-adcp-server.d.ts +21 -0
  92. package/dist/lib/server/create-adcp-server.js +98 -16
  93. package/dist/lib/server/create-adcp-server.mjs +84 -2
  94. package/dist/lib/server/error-arm-tools.d.mts +10 -10
  95. package/dist/lib/server/error-arm-tools.d.ts +10 -10
  96. package/dist/lib/server/error-arm-tools.js +22 -58
  97. package/dist/lib/server/error-arm-tools.mjs +30 -65
  98. package/dist/lib/server/index.d.mts +1 -1
  99. package/dist/lib/server/index.d.ts +1 -1
  100. package/dist/lib/storage/interfaces.d.mts +2 -0
  101. package/dist/lib/storage/interfaces.d.ts +2 -0
  102. package/dist/lib/testing/compliance/comply.js +17 -0
  103. package/dist/lib/testing/compliance/comply.mjs +17 -0
  104. package/dist/lib/testing/index.d.mts +3 -0
  105. package/dist/lib/testing/index.d.ts +3 -0
  106. package/dist/lib/testing/index.js +44 -2
  107. package/dist/lib/testing/index.mjs +42 -0
  108. package/dist/lib/testing/storyboard/collection-selection.d.mts +259 -0
  109. package/dist/lib/testing/storyboard/collection-selection.d.ts +259 -0
  110. package/dist/lib/testing/storyboard/collection-selection.js +384 -0
  111. package/dist/lib/testing/storyboard/collection-selection.mjs +348 -0
  112. package/dist/lib/testing/storyboard/context.js +12 -6
  113. package/dist/lib/testing/storyboard/context.mjs +12 -6
  114. package/dist/lib/testing/storyboard/fixture-resolution.d.mts +39 -1
  115. package/dist/lib/testing/storyboard/fixture-resolution.d.ts +39 -1
  116. package/dist/lib/testing/storyboard/fixture-resolution.js +216 -12
  117. package/dist/lib/testing/storyboard/fixture-resolution.mjs +218 -12
  118. package/dist/lib/testing/storyboard/index.d.mts +9 -2
  119. package/dist/lib/testing/storyboard/index.d.ts +9 -2
  120. package/dist/lib/testing/storyboard/index.js +51 -2
  121. package/dist/lib/testing/storyboard/index.mjs +49 -1
  122. package/dist/lib/testing/storyboard/prepared-fixtures.d.mts +471 -0
  123. package/dist/lib/testing/storyboard/prepared-fixtures.d.ts +471 -0
  124. package/dist/lib/testing/storyboard/prepared-fixtures.js +434 -0
  125. package/dist/lib/testing/storyboard/prepared-fixtures.mjs +389 -0
  126. package/dist/lib/testing/storyboard/protected-context.d.mts +127 -0
  127. package/dist/lib/testing/storyboard/protected-context.d.ts +127 -0
  128. package/dist/lib/testing/storyboard/protected-context.js +301 -0
  129. package/dist/lib/testing/storyboard/protected-context.mjs +267 -0
  130. package/dist/lib/testing/storyboard/rejection-hints.js +13 -4
  131. package/dist/lib/testing/storyboard/rejection-hints.mjs +13 -4
  132. package/dist/lib/testing/storyboard/runner.d.mts +24 -0
  133. package/dist/lib/testing/storyboard/runner.d.ts +24 -0
  134. package/dist/lib/testing/storyboard/runner.js +1061 -105
  135. package/dist/lib/testing/storyboard/runner.mjs +1075 -106
  136. package/dist/lib/testing/storyboard/seeding.d.mts +15 -1
  137. package/dist/lib/testing/storyboard/seeding.d.ts +15 -1
  138. package/dist/lib/testing/storyboard/seeding.js +272 -12
  139. package/dist/lib/testing/storyboard/seeding.mjs +274 -13
  140. package/dist/lib/testing/storyboard/types.d.mts +207 -10
  141. package/dist/lib/testing/storyboard/types.d.ts +207 -10
  142. package/dist/lib/testing/storyboard/types.js +1 -1
  143. package/dist/lib/testing/storyboard/types.mjs +1 -1
  144. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  145. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  146. package/dist/lib/testing/storyboard/validations.js +95 -0
  147. package/dist/lib/testing/storyboard/validations.mjs +95 -0
  148. package/dist/lib/validation/bundled-schema-store.js +5 -0
  149. package/dist/lib/validation/bundled-schema-store.mjs +4 -0
  150. package/dist/lib/validation/schema-loader.js +19 -2
  151. package/dist/lib/validation/schema-loader.mjs +22 -3
  152. package/dist/lib/version.d.mts +3 -3
  153. package/dist/lib/version.d.ts +3 -3
  154. package/dist/lib/version.js +3 -3
  155. package/dist/lib/version.mjs +3 -3
  156. package/docs/TYPE-SUMMARY.md +2 -2
  157. package/docs/guides/COLLECTION-FIXTURE-FOUNDATION.md +850 -0
  158. package/docs/guides/PROPOSAL-TERMS-VERIFICATION.md +66 -0
  159. package/docs/llms.txt +4 -3
  160. package/docs/migration-14.x-rc-worksheet.md +4 -4
  161. package/package.json +1 -1
@@ -0,0 +1,348 @@
1
+ const PAIR_LIMIT_DEFAULT = 1e4;
2
+ const COLLECTION_PAIR_LIMIT = PAIR_LIMIT_DEFAULT;
3
+ const COLLECTION_DIAGNOSTIC_LIMIT = 20;
4
+ function isRecord(value) {
5
+ return value !== null && typeof value === "object" && !Array.isArray(value);
6
+ }
7
+ function utf8Compare(a, b) {
8
+ return Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
9
+ }
10
+ function compareCollectionPairs(a, b) {
11
+ const domain = utf8Compare(a.publisher_domain, b.publisher_domain);
12
+ if (domain !== 0) return domain;
13
+ if (a.collection_id === b.collection_id) return 0;
14
+ if (a.collection_id === null) return -1;
15
+ if (b.collection_id === null) return 1;
16
+ return utf8Compare(a.collection_id, b.collection_id);
17
+ }
18
+ function formatCollectionPair(pair) {
19
+ return pair.collection_id === null ? `${JSON.stringify(pair.publisher_domain)}:*` : `${JSON.stringify(pair.publisher_domain)}:${JSON.stringify(pair.collection_id)}`;
20
+ }
21
+ function pairKey(pair) {
22
+ return pair.collection_id === null ? `${JSON.stringify(pair.publisher_domain)}\0` : `${JSON.stringify(pair.publisher_domain)}\0${JSON.stringify(pair.collection_id)}`;
23
+ }
24
+ function normalizeCollectionSelectors(value, options = {}) {
25
+ const limit = options.limit ?? PAIR_LIMIT_DEFAULT;
26
+ if (!Array.isArray(value)) {
27
+ return {
28
+ ok: false,
29
+ failure: {
30
+ code: "not_an_array",
31
+ detail: `expected an array of collection selectors; received ${describe(value)}`
32
+ }
33
+ };
34
+ }
35
+ const seen = /* @__PURE__ */ new Map();
36
+ for (let index = 0; index < value.length; index++) {
37
+ const selector = value[index];
38
+ if (!isRecord(selector)) {
39
+ return {
40
+ ok: false,
41
+ failure: {
42
+ code: "selector_not_object",
43
+ detail: `collections[${index}] must be an object; received ${describe(selector)}`
44
+ }
45
+ };
46
+ }
47
+ const domain = selector.publisher_domain;
48
+ if (typeof domain !== "string" || domain.length === 0) {
49
+ return {
50
+ ok: false,
51
+ failure: {
52
+ code: "publisher_domain_invalid",
53
+ detail: `collections[${index}].publisher_domain must be a non-empty string; received ${describe(domain)}`
54
+ }
55
+ };
56
+ }
57
+ const ids = selector.collection_ids;
58
+ if (ids === void 0 || ids === null) {
59
+ addPair(seen, { publisher_domain: domain, collection_id: null });
60
+ } else {
61
+ if (!Array.isArray(ids) || ids.length === 0) {
62
+ return {
63
+ ok: false,
64
+ failure: {
65
+ code: "collection_ids_invalid",
66
+ detail: `collections[${index}].collection_ids must be a non-empty array when present; received ${describe(ids)}`
67
+ }
68
+ };
69
+ }
70
+ for (let idIndex = 0; idIndex < ids.length; idIndex++) {
71
+ const id = ids[idIndex];
72
+ if (typeof id !== "string") {
73
+ return {
74
+ ok: false,
75
+ failure: {
76
+ code: "collection_id_invalid",
77
+ detail: `collections[${index}].collection_ids[${idIndex}] must be a string; received ${describe(id)}`
78
+ }
79
+ };
80
+ }
81
+ addPair(seen, { publisher_domain: domain, collection_id: id });
82
+ if (seen.size > limit) return pairLimitExceeded(limit, index);
83
+ }
84
+ }
85
+ if (seen.size > limit) return pairLimitExceeded(limit, index);
86
+ }
87
+ const pairs = [...seen.values()].sort(compareCollectionPairs);
88
+ const domains = [...new Set(pairs.map((pair) => pair.publisher_domain))].sort(utf8Compare);
89
+ return { ok: true, value: { pairs, selector_count: value.length, domains } };
90
+ }
91
+ function pairLimitExceeded(limit, index) {
92
+ return {
93
+ ok: false,
94
+ failure: {
95
+ code: "pair_limit_exceeded",
96
+ detail: `normalized collection pairs exceeded the ${limit}-pair safety limit at collections[${index}]`
97
+ }
98
+ };
99
+ }
100
+ function addPair(seen, pair) {
101
+ const key = pairKey(pair);
102
+ if (!seen.has(key)) seen.set(key, pair);
103
+ }
104
+ function describe(value) {
105
+ if (value === void 0) return "undefined";
106
+ if (value === null) return "null";
107
+ if (Array.isArray(value)) return `an array of length ${value.length}`;
108
+ return typeof value === "object" ? "an object" : JSON.stringify(value);
109
+ }
110
+ function diffCollectionPairSets(actual, expected) {
111
+ const expectedKeys = new Set(expected.map(pairKey));
112
+ const actualKeys = new Set(actual.map(pairKey));
113
+ const missing = expected.filter((pair) => !actualKeys.has(pairKey(pair)));
114
+ const unexpected = actual.filter((pair) => !expectedKeys.has(pairKey(pair)));
115
+ return { equal: missing.length === 0 && unexpected.length === 0, missing, unexpected };
116
+ }
117
+ function summarizeCollectionPairs(pairs) {
118
+ const shown = pairs.slice(0, COLLECTION_DIAGNOSTIC_LIMIT).map(formatCollectionPair);
119
+ const elided = pairs.length - shown.length;
120
+ return elided > 0 ? `${shown.join(", ")} (+${elided} more)` : shown.join(", ");
121
+ }
122
+ const COLLECTION_TARGETING_MODES = ["selectable", "fixed", "any"];
123
+ function normalizeCollectionSelectionSelector(value, location) {
124
+ if (!isRecord(value)) throw new Error(`${location}: must be an object`);
125
+ const unknown = Object.keys(value).filter(
126
+ (key) => !["collection_targeting", "minimum_pairs", "require_cross_publisher_collection"].includes(key)
127
+ );
128
+ if (unknown.length > 0) {
129
+ throw new Error(`${location}: unknown key(s): ${unknown.join(", ")}; the operand is closed`);
130
+ }
131
+ const targeting = value.collection_targeting;
132
+ if (typeof targeting !== "string" || !COLLECTION_TARGETING_MODES.includes(targeting)) {
133
+ throw new Error(`${location}.collection_targeting: must be one of ${COLLECTION_TARGETING_MODES.join(", ")}`);
134
+ }
135
+ let minimumPairs = 1;
136
+ if (value.minimum_pairs !== void 0) {
137
+ if (value.minimum_pairs !== 1 && value.minimum_pairs !== 2) {
138
+ throw new Error(`${location}.minimum_pairs: must be 1 or 2`);
139
+ }
140
+ minimumPairs = value.minimum_pairs;
141
+ }
142
+ if (value.require_cross_publisher_collection !== void 0 && typeof value.require_cross_publisher_collection !== "boolean") {
143
+ throw new Error(`${location}.require_cross_publisher_collection: must be boolean`);
144
+ }
145
+ return {
146
+ collection_targeting: targeting,
147
+ minimum_pairs: minimumPairs,
148
+ require_cross_publisher_collection: value.require_cross_publisher_collection === true
149
+ };
150
+ }
151
+ function collectHostDomains(candidate) {
152
+ const domains = /* @__PURE__ */ new Set();
153
+ const properties = candidate.publisher_properties;
154
+ if (Array.isArray(properties)) {
155
+ for (const entry of properties) {
156
+ if (!isRecord(entry)) continue;
157
+ if (typeof entry.publisher_domain === "string" && entry.publisher_domain.length > 0) {
158
+ domains.add(entry.publisher_domain);
159
+ }
160
+ }
161
+ }
162
+ return [...domains].sort(utf8Compare);
163
+ }
164
+ function cloneSelectorBundle(value) {
165
+ return value.map((selector) => ({
166
+ publisher_domain: selector.publisher_domain,
167
+ ...Array.isArray(selector.collection_ids) && { collection_ids: [...selector.collection_ids] }
168
+ }));
169
+ }
170
+ function buildUndeclaredCollectionIdSelectors(domain, pairs) {
171
+ const taken = new Set(pairs.filter((pair) => pair.publisher_domain === domain).map((pair) => pair.collection_id));
172
+ let candidate = "adcp-absent-collection";
173
+ for (let suffix = 0; taken.has(candidate); suffix++) {
174
+ candidate = `adcp-absent-collection-${suffix}`;
175
+ }
176
+ return [{ publisher_domain: domain, collection_ids: [candidate] }];
177
+ }
178
+ function buildUndeclaredPublisherDomainSelectors(domains, collectionId) {
179
+ let candidate = "adcp-absent-owner.invalid";
180
+ for (let suffix = 0; domains.has(candidate); suffix++) {
181
+ candidate = `adcp-absent-owner-${suffix}.invalid`;
182
+ }
183
+ return [{ publisher_domain: candidate, collection_ids: [collectionId] }];
184
+ }
185
+ function evaluateCollectionSelection(candidate, selector) {
186
+ if (selector !== null && typeof selector === "object" && "declared_selectors" in selector) {
187
+ throw new Error(
188
+ 'evaluateCollectionSelection: received a CollectionSelectionWitness where a CollectionSelectionSelector was expected. A witness is the OUTPUT of an evaluation, not an operand; build the operand explicitly, e.g. { collection_targeting: "selectable", minimum_pairs: 2 }.'
189
+ );
190
+ }
191
+ const resolved = {
192
+ collection_targeting: selector.collection_targeting,
193
+ minimum_pairs: selector.minimum_pairs ?? 1,
194
+ require_cross_publisher_collection: selector.require_cross_publisher_collection === true
195
+ };
196
+ return evaluateResolvedCollectionSelection(candidate, resolved);
197
+ }
198
+ function evaluateResolvedCollectionSelection(candidate, selector) {
199
+ if (!isRecord(candidate)) return { eligible: false, reason: "candidate is not an object" };
200
+ const flag = candidate.collection_targeting_allowed;
201
+ if (flag !== void 0 && typeof flag !== "boolean") {
202
+ return {
203
+ eligible: false,
204
+ reason: `collection_targeting_allowed must be boolean or absent; observed ${describe(flag)}`
205
+ };
206
+ }
207
+ const effectiveTargeting = flag === true ? "selectable" : "fixed";
208
+ if (selector.collection_targeting !== "any" && selector.collection_targeting !== effectiveTargeting) {
209
+ return {
210
+ eligible: false,
211
+ reason: selector.collection_targeting === "fixed" ? "collection_targeting_allowed is true; a fixed bundle requires literal false or absence" : "collection_targeting_allowed is not literal true"
212
+ };
213
+ }
214
+ const normalized = normalizeCollectionSelectors(candidate.collections);
215
+ if (!normalized.ok) {
216
+ return { eligible: false, reason: `collections did not normalize: ${normalized.failure.detail}` };
217
+ }
218
+ if (normalized.value.pairs.some((pair) => pair.collection_id === null)) {
219
+ return {
220
+ eligible: false,
221
+ reason: "product collections contain a domain-only bulk-grant selector; product composition requires explicit collection_ids on every entry"
222
+ };
223
+ }
224
+ const concrete = normalized.value.pairs;
225
+ if (concrete.length < selector.minimum_pairs) {
226
+ return {
227
+ eligible: false,
228
+ reason: `product exposes ${concrete.length} distinct concrete collection pair(s); ${selector.minimum_pairs} required`
229
+ };
230
+ }
231
+ const hostDomains = collectHostDomains(candidate);
232
+ const bundle = cloneSelectorBundle(candidate.collections);
233
+ let relation = null;
234
+ if (selector.require_cross_publisher_collection) {
235
+ const hostSet = new Set(hostDomains);
236
+ const externalDomain = normalized.value.domains.find((domain) => !hostSet.has(domain));
237
+ if (externalDomain === void 0) {
238
+ return {
239
+ eligible: false,
240
+ reason: hostDomains.length === 0 ? "product declares no publisher_properties domains, so no owner/host relation is observable" : "every collection namespace is also a declared property namespace"
241
+ };
242
+ }
243
+ const carrying = bundle.filter((entry) => entry.publisher_domain === externalDomain).sort((left, right) => {
244
+ const l = JSON.stringify([...left.collection_ids ?? []].sort());
245
+ const r = JSON.stringify([...right.collection_ids ?? []].sort());
246
+ return l < r ? -1 : l > r ? 1 : 0;
247
+ })[0];
248
+ relation = {
249
+ collection_publisher_domain: externalDomain,
250
+ property_publisher_domains: hostDomains,
251
+ selector: {
252
+ ...carrying,
253
+ ...carrying.collection_ids && { collection_ids: [...carrying.collection_ids] }
254
+ }
255
+ };
256
+ }
257
+ const subsetSource = relation === null ? concrete[0] : (
258
+ // Non-null is sound, not optimism: `relation.collection_publisher_domain`
259
+ // is taken from `normalized.value.domains`, which is derived from
260
+ // `concrete`, so the domain is present by construction. The previous
261
+ // `?? concrete[0]` read as a carriage-vs-local fallback and was not
262
+ // one — it could only ever have masked a normalizer bug.
263
+ concrete.find((pair) => pair.publisher_domain === relation.collection_publisher_domain)
264
+ );
265
+ const strictSubset = concrete.length >= 2 ? [{ publisher_domain: subsetSource.publisher_domain, collection_ids: [subsetSource.collection_id] }] : null;
266
+ const witness = {
267
+ product_id: typeof candidate.product_id === "string" ? candidate.product_id : null,
268
+ collection_targeting: effectiveTargeting,
269
+ collection_targeting_allowed: flag,
270
+ declared_selectors: bundle,
271
+ distinct_pairs: concrete,
272
+ strict_subset: strictSubset,
273
+ undeclared_collection_id_selectors: buildUndeclaredCollectionIdSelectors(
274
+ subsetSource.publisher_domain,
275
+ normalized.value.pairs
276
+ ),
277
+ undeclared_publisher_domain_selectors: buildUndeclaredPublisherDomainSelectors(
278
+ new Set(normalized.value.domains),
279
+ subsetSource.collection_id
280
+ ),
281
+ cross_publisher_relation: relation,
282
+ property_publisher_domains: hostDomains
283
+ };
284
+ return { eligible: true, witness };
285
+ }
286
+ function productSatisfiesCollectionSelection(candidate, selector) {
287
+ return evaluateCollectionSelection(candidate, selector).eligible;
288
+ }
289
+ const COLLECTION_WITNESS_KINDS = [
290
+ "declared_selectors",
291
+ "distinct_pairs",
292
+ "strict_subset",
293
+ "undeclared_collection_id_selectors",
294
+ "undeclared_publisher_domain_selectors",
295
+ "cross_publisher_relation",
296
+ "property_publisher_domains"
297
+ ];
298
+ function readCollectionWitness(witness, kind) {
299
+ if (!witness) {
300
+ return {
301
+ ok: false,
302
+ detail: `witness capture "${kind}" requires a collection_selection_satisfies predicate on the same declaration`
303
+ };
304
+ }
305
+ switch (kind) {
306
+ case "declared_selectors":
307
+ return { ok: true, value: witness.declared_selectors };
308
+ case "distinct_pairs":
309
+ return { ok: true, value: witness.distinct_pairs };
310
+ case "strict_subset":
311
+ if (witness.strict_subset === null) {
312
+ return {
313
+ ok: false,
314
+ detail: "strict_subset requires a product with at least two distinct concrete collection pairs; declare minimum_pairs: 2"
315
+ };
316
+ }
317
+ return { ok: true, value: witness.strict_subset };
318
+ case "undeclared_collection_id_selectors":
319
+ return { ok: true, value: witness.undeclared_collection_id_selectors };
320
+ case "undeclared_publisher_domain_selectors":
321
+ return { ok: true, value: witness.undeclared_publisher_domain_selectors };
322
+ case "cross_publisher_relation":
323
+ if (witness.cross_publisher_relation === null) {
324
+ return {
325
+ ok: false,
326
+ detail: "cross_publisher_relation requires require_cross_publisher_collection: true on the collection_selection_satisfies operand"
327
+ };
328
+ }
329
+ return { ok: true, value: witness.cross_publisher_relation };
330
+ case "property_publisher_domains":
331
+ return { ok: true, value: witness.property_publisher_domains };
332
+ }
333
+ }
334
+ export {
335
+ COLLECTION_DIAGNOSTIC_LIMIT,
336
+ COLLECTION_PAIR_LIMIT,
337
+ COLLECTION_TARGETING_MODES,
338
+ COLLECTION_WITNESS_KINDS,
339
+ compareCollectionPairs,
340
+ diffCollectionPairSets,
341
+ evaluateCollectionSelection,
342
+ formatCollectionPair,
343
+ normalizeCollectionSelectionSelector,
344
+ normalizeCollectionSelectors,
345
+ productSatisfiesCollectionSelection,
346
+ readCollectionWitness,
347
+ summarizeCollectionPairs
348
+ };
@@ -32,6 +32,7 @@ __export(context_exports, {
32
32
  module.exports = __toCommonJS(context_exports);
33
33
  var import_node_crypto = require("node:crypto");
34
34
  var import_path = require('./path.js');
35
+ var import_protected_context = require('./protected-context.js');
35
36
  var import_media_buy_status = require('../../utils/media-buy-status.js');
36
37
  var import_path2 = require('./path.js');
37
38
  const PRODUCT_CONTEXT_KEYS = [
@@ -393,7 +394,7 @@ const aliasCaches = /* @__PURE__ */ new WeakMap();
393
394
  function getAliasCache(context) {
394
395
  let cache = aliasCaches.get(context);
395
396
  if (!cache) {
396
- cache = {};
397
+ cache = /* @__PURE__ */ Object.create(null);
397
398
  aliasCaches.set(context, cache);
398
399
  }
399
400
  return cache;
@@ -419,14 +420,15 @@ function deepReplace(value, context, runnerVars) {
419
420
  const ctxMatch = expanded.match(/^\$context\.(\w+)$/);
420
421
  if (ctxMatch?.[1]) {
421
422
  const key = ctxMatch[1];
422
- return key in context ? context[key] : expanded;
423
+ if (!Object.hasOwn(context, key)) return expanded;
424
+ return (0, import_protected_context.isProtectedContextKey)(key) ? (0, import_protected_context.detachProtectedValue)(context[key]) : context[key];
423
425
  }
424
426
  const genMatch = expanded.match(/^\$generate:(uuid_v4|opaque_id)(?:#([A-Za-z0-9_.-]+))?$/);
425
427
  if (genMatch) {
426
428
  const alias = genMatch[2];
427
429
  if (alias) {
428
430
  const cache = getAliasCache(context);
429
- if (!(alias in cache)) cache[alias] = (0, import_node_crypto.randomUUID)();
431
+ if (!Object.hasOwn(cache, alias)) cache[alias] = (0, import_node_crypto.randomUUID)();
430
432
  return cache[alias];
431
433
  }
432
434
  return (0, import_node_crypto.randomUUID)();
@@ -521,7 +523,7 @@ function applyContextOutputsWithProvenance(data, outputs, stepId, taskName, cont
521
523
  }
522
524
  const cache = getAliasCache(context);
523
525
  let value;
524
- if (output.key in cache) {
526
+ if (Object.hasOwn(cache, output.key)) {
525
527
  value = cache[output.key];
526
528
  } else {
527
529
  value = (0, import_node_crypto.randomUUID)();
@@ -558,8 +560,12 @@ function applyContextOutputsWithProvenance(data, outputs, stepId, taskName, cont
558
560
  function applyContextInputs(request, inputs, context) {
559
561
  const result = structuredClone(request);
560
562
  for (const input of inputs) {
561
- if (input.key in context) {
562
- (0, import_path.setPath)(result, input.inject_at, context[input.key]);
563
+ if (Object.hasOwn(context, input.key)) {
564
+ (0, import_path.setPath)(
565
+ result,
566
+ input.inject_at,
567
+ (0, import_protected_context.isProtectedContextKey)(input.key) ? (0, import_protected_context.detachProtectedValue)(context[input.key]) : context[input.key]
568
+ );
563
569
  }
564
570
  }
565
571
  return result;
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { resolvePath, setPath } from "./path.mjs";
3
+ import { detachProtectedValue, isProtectedContextKey } from "./protected-context.mjs";
3
4
  import { getAuthoritativeMediaBuyStatus } from "../../utils/media-buy-status.mjs";
4
5
  const PRODUCT_CONTEXT_KEYS = [
5
6
  "product",
@@ -360,7 +361,7 @@ const aliasCaches = /* @__PURE__ */ new WeakMap();
360
361
  function getAliasCache(context) {
361
362
  let cache = aliasCaches.get(context);
362
363
  if (!cache) {
363
- cache = {};
364
+ cache = /* @__PURE__ */ Object.create(null);
364
365
  aliasCaches.set(context, cache);
365
366
  }
366
367
  return cache;
@@ -386,14 +387,15 @@ function deepReplace(value, context, runnerVars) {
386
387
  const ctxMatch = expanded.match(/^\$context\.(\w+)$/);
387
388
  if (ctxMatch?.[1]) {
388
389
  const key = ctxMatch[1];
389
- return key in context ? context[key] : expanded;
390
+ if (!Object.hasOwn(context, key)) return expanded;
391
+ return isProtectedContextKey(key) ? detachProtectedValue(context[key]) : context[key];
390
392
  }
391
393
  const genMatch = expanded.match(/^\$generate:(uuid_v4|opaque_id)(?:#([A-Za-z0-9_.-]+))?$/);
392
394
  if (genMatch) {
393
395
  const alias = genMatch[2];
394
396
  if (alias) {
395
397
  const cache = getAliasCache(context);
396
- if (!(alias in cache)) cache[alias] = randomUUID();
398
+ if (!Object.hasOwn(cache, alias)) cache[alias] = randomUUID();
397
399
  return cache[alias];
398
400
  }
399
401
  return randomUUID();
@@ -488,7 +490,7 @@ function applyContextOutputsWithProvenance(data, outputs, stepId, taskName, cont
488
490
  }
489
491
  const cache = getAliasCache(context);
490
492
  let value;
491
- if (output.key in cache) {
493
+ if (Object.hasOwn(cache, output.key)) {
492
494
  value = cache[output.key];
493
495
  } else {
494
496
  value = randomUUID();
@@ -525,8 +527,12 @@ function applyContextOutputsWithProvenance(data, outputs, stepId, taskName, cont
525
527
  function applyContextInputs(request, inputs, context) {
526
528
  const result = structuredClone(request);
527
529
  for (const input of inputs) {
528
- if (input.key in context) {
529
- setPath(result, input.inject_at, context[input.key]);
530
+ if (Object.hasOwn(context, input.key)) {
531
+ setPath(
532
+ result,
533
+ input.inject_at,
534
+ isProtectedContextKey(input.key) ? detachProtectedValue(context[input.key]) : context[input.key]
535
+ );
530
536
  }
531
537
  }
532
538
  return result;
@@ -1,11 +1,47 @@
1
1
  import { type SchemaEntityPath } from '../../validation/schema-loader.mjs';
2
- import type { FixtureMatchClause, FixtureResolutionStrategy, Storyboard } from './types.mjs';
2
+ import { type CollectionSelectionWitness } from './collection-selection.mjs';
3
+ import type { FixtureContextOutput, FixtureMatchClause, FixtureResolutionStrategy, Storyboard } from './types.mjs';
3
4
  /** Normalize and validate the closed fixture matching DSL. */
4
5
  export declare function normalizeFixtureMatchExpression(expression: unknown, location: string): FixtureMatchClause[];
6
+ /**
7
+ * Validate the declaration-local capture block.
8
+ *
9
+ * A `witness:` entry is only authorable when the same declaration carries a
10
+ * `collection_selection_satisfies` predicate — the witness is retained from
11
+ * that evaluation, so asking for one without the predicate is an authoring
12
+ * error rather than a runtime surprise on an agent that happens to answer
13
+ * differently.
14
+ */
15
+ export declare function normalizeFixtureContextOutputs(value: unknown, location: string, clauses: readonly FixtureMatchClause[],
16
+ /**
17
+ * `'pricing_option'` refuses `witness:` captures. A collection witness
18
+ * describes a PRODUCT — its selectors, its pairs, its cross-publisher
19
+ * relation — and a pricing option has none of its own. Accepting one
20
+ * here loaded fine and then failed at run with a message about a missing
21
+ * `collection_selection_satisfies` predicate that was right there in the
22
+ * declaration, and the two strategies disagreed about it besides (seed
23
+ * retained the parent's witness, discover did not). Declare the witness
24
+ * on the parent product.
25
+ */
26
+ entityKind?: 'product' | 'pricing_option'): FixtureContextOutput[];
5
27
  /** Loader/runtime authoring validation for the AdCP 3.2 declaration block. */
6
28
  export declare function validateFixtureResolutionDeclarations(storyboard: Storyboard): void;
7
29
  /** True when a seller catalog entity satisfies every authored clause. */
8
30
  export declare function matchesFixtureRequirements(candidate: unknown, clauses: readonly FixtureMatchClause[]): boolean;
31
+ /**
32
+ * Match a candidate and retain the collection witness for it.
33
+ *
34
+ * The boolean `matchesFixtureRequirements` keeps its signature for existing
35
+ * callers. This variant exists so the resolver can retain the witness for the
36
+ * candidate it *actually selects*, rather than recomputing one later from an
37
+ * unrelated response — which is precisely how a selected-entity snapshot
38
+ * drifts away from the entity that was purchased.
39
+ */
40
+ export declare function matchFixtureRequirementsWithWitness(candidate: unknown, clauses: readonly FixtureMatchClause[]): {
41
+ matched: boolean;
42
+ witness?: CollectionSelectionWitness;
43
+ reason?: string;
44
+ };
9
45
  export interface FixtureResolutionSpec {
10
46
  entityType: 'product' | 'product_pricing_option';
11
47
  handle: string;
@@ -14,6 +50,8 @@ export interface FixtureResolutionSpec {
14
50
  strategies: FixtureResolutionStrategy[];
15
51
  clauses: FixtureMatchClause[];
16
52
  allowReuse: boolean;
53
+ /** Declaration-local captures published after the whole graph resolves. */
54
+ contextOutputs: FixtureContextOutput[];
17
55
  }
18
56
  /** Normalize fixture rows + optional metadata into the ordered handle list. */
19
57
  export declare function buildFixtureResolutionSpecs(storyboard: Storyboard): FixtureResolutionSpec[];
@@ -1,11 +1,47 @@
1
1
  import { type SchemaEntityPath } from '../../validation/schema-loader';
2
- import type { FixtureMatchClause, FixtureResolutionStrategy, Storyboard } from './types';
2
+ import { type CollectionSelectionWitness } from './collection-selection';
3
+ import type { FixtureContextOutput, FixtureMatchClause, FixtureResolutionStrategy, Storyboard } from './types';
3
4
  /** Normalize and validate the closed fixture matching DSL. */
4
5
  export declare function normalizeFixtureMatchExpression(expression: unknown, location: string): FixtureMatchClause[];
6
+ /**
7
+ * Validate the declaration-local capture block.
8
+ *
9
+ * A `witness:` entry is only authorable when the same declaration carries a
10
+ * `collection_selection_satisfies` predicate — the witness is retained from
11
+ * that evaluation, so asking for one without the predicate is an authoring
12
+ * error rather than a runtime surprise on an agent that happens to answer
13
+ * differently.
14
+ */
15
+ export declare function normalizeFixtureContextOutputs(value: unknown, location: string, clauses: readonly FixtureMatchClause[],
16
+ /**
17
+ * `'pricing_option'` refuses `witness:` captures. A collection witness
18
+ * describes a PRODUCT — its selectors, its pairs, its cross-publisher
19
+ * relation — and a pricing option has none of its own. Accepting one
20
+ * here loaded fine and then failed at run with a message about a missing
21
+ * `collection_selection_satisfies` predicate that was right there in the
22
+ * declaration, and the two strategies disagreed about it besides (seed
23
+ * retained the parent's witness, discover did not). Declare the witness
24
+ * on the parent product.
25
+ */
26
+ entityKind?: 'product' | 'pricing_option'): FixtureContextOutput[];
5
27
  /** Loader/runtime authoring validation for the AdCP 3.2 declaration block. */
6
28
  export declare function validateFixtureResolutionDeclarations(storyboard: Storyboard): void;
7
29
  /** True when a seller catalog entity satisfies every authored clause. */
8
30
  export declare function matchesFixtureRequirements(candidate: unknown, clauses: readonly FixtureMatchClause[]): boolean;
31
+ /**
32
+ * Match a candidate and retain the collection witness for it.
33
+ *
34
+ * The boolean `matchesFixtureRequirements` keeps its signature for existing
35
+ * callers. This variant exists so the resolver can retain the witness for the
36
+ * candidate it *actually selects*, rather than recomputing one later from an
37
+ * unrelated response — which is precisely how a selected-entity snapshot
38
+ * drifts away from the entity that was purchased.
39
+ */
40
+ export declare function matchFixtureRequirementsWithWitness(candidate: unknown, clauses: readonly FixtureMatchClause[]): {
41
+ matched: boolean;
42
+ witness?: CollectionSelectionWitness;
43
+ reason?: string;
44
+ };
9
45
  export interface FixtureResolutionSpec {
10
46
  entityType: 'product' | 'product_pricing_option';
11
47
  handle: string;
@@ -14,6 +50,8 @@ export interface FixtureResolutionSpec {
14
50
  strategies: FixtureResolutionStrategy[];
15
51
  clauses: FixtureMatchClause[];
16
52
  allowReuse: boolean;
53
+ /** Declaration-local captures published after the whole graph resolves. */
54
+ contextOutputs: FixtureContextOutput[];
17
55
  }
18
56
  /** Normalize fixture rows + optional metadata into the ordered handle list. */
19
57
  export declare function buildFixtureResolutionSpecs(storyboard: Storyboard): FixtureResolutionSpec[];