@adcp/sdk 16.2.0 → 16.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/AGENTS.md +2 -0
  2. package/dist/lib/core/AgentClient.js +17 -1
  3. package/dist/lib/core/AgentClient.mjs +17 -1
  4. package/dist/lib/core/SingleAgentClient.js +13 -0
  5. package/dist/lib/core/SingleAgentClient.mjs +13 -0
  6. package/dist/lib/core/TaskExecutor.js +88 -13
  7. package/dist/lib/core/TaskExecutor.mjs +89 -14
  8. package/dist/lib/errors/index.d.mts +34 -0
  9. package/dist/lib/errors/index.d.ts +34 -0
  10. package/dist/lib/errors/index.js +11 -0
  11. package/dist/lib/errors/index.mjs +10 -0
  12. package/dist/lib/index.d.mts +1 -1
  13. package/dist/lib/index.d.ts +1 -1
  14. package/dist/lib/index.js +2 -0
  15. package/dist/lib/index.mjs +2 -0
  16. package/dist/lib/media-buy/compatibility.d.mts +1 -0
  17. package/dist/lib/media-buy/compatibility.d.ts +1 -0
  18. package/dist/lib/media-buy/compatibility.js +33 -10
  19. package/dist/lib/media-buy/compatibility.mjs +32 -14
  20. package/dist/lib/negotiation/buyer.js +11 -5
  21. package/dist/lib/negotiation/buyer.mjs +11 -5
  22. package/dist/lib/negotiation/types.d.mts +4 -2
  23. package/dist/lib/negotiation/types.d.ts +4 -2
  24. package/dist/lib/negotiation/verification.d.mts +15 -6
  25. package/dist/lib/negotiation/verification.d.ts +15 -6
  26. package/dist/lib/negotiation/verification.js +146 -27
  27. package/dist/lib/negotiation/verification.mjs +146 -28
  28. package/dist/lib/negotiation/version.d.mts +6 -0
  29. package/dist/lib/negotiation/version.d.ts +6 -0
  30. package/dist/lib/negotiation/version.js +60 -0
  31. package/dist/lib/negotiation/version.mjs +33 -0
  32. package/dist/lib/server/create-adcp-server.js +1 -1
  33. package/dist/lib/server/create-adcp-server.mjs +1 -1
  34. package/dist/lib/server/error-arm-tools.d.mts +10 -10
  35. package/dist/lib/server/error-arm-tools.d.ts +10 -10
  36. package/dist/lib/server/error-arm-tools.js +22 -58
  37. package/dist/lib/server/error-arm-tools.mjs +30 -65
  38. package/dist/lib/testing/compliance/comply.js +17 -0
  39. package/dist/lib/testing/compliance/comply.mjs +17 -0
  40. package/dist/lib/testing/index.d.mts +3 -0
  41. package/dist/lib/testing/index.d.ts +3 -0
  42. package/dist/lib/testing/index.js +44 -2
  43. package/dist/lib/testing/index.mjs +42 -0
  44. package/dist/lib/testing/storyboard/collection-selection.d.mts +259 -0
  45. package/dist/lib/testing/storyboard/collection-selection.d.ts +259 -0
  46. package/dist/lib/testing/storyboard/collection-selection.js +384 -0
  47. package/dist/lib/testing/storyboard/collection-selection.mjs +348 -0
  48. package/dist/lib/testing/storyboard/context.js +12 -6
  49. package/dist/lib/testing/storyboard/context.mjs +12 -6
  50. package/dist/lib/testing/storyboard/fixture-resolution.d.mts +39 -1
  51. package/dist/lib/testing/storyboard/fixture-resolution.d.ts +39 -1
  52. package/dist/lib/testing/storyboard/fixture-resolution.js +216 -12
  53. package/dist/lib/testing/storyboard/fixture-resolution.mjs +218 -12
  54. package/dist/lib/testing/storyboard/index.d.mts +9 -2
  55. package/dist/lib/testing/storyboard/index.d.ts +9 -2
  56. package/dist/lib/testing/storyboard/index.js +51 -2
  57. package/dist/lib/testing/storyboard/index.mjs +49 -1
  58. package/dist/lib/testing/storyboard/prepared-fixtures.d.mts +471 -0
  59. package/dist/lib/testing/storyboard/prepared-fixtures.d.ts +471 -0
  60. package/dist/lib/testing/storyboard/prepared-fixtures.js +434 -0
  61. package/dist/lib/testing/storyboard/prepared-fixtures.mjs +389 -0
  62. package/dist/lib/testing/storyboard/protected-context.d.mts +127 -0
  63. package/dist/lib/testing/storyboard/protected-context.d.ts +127 -0
  64. package/dist/lib/testing/storyboard/protected-context.js +301 -0
  65. package/dist/lib/testing/storyboard/protected-context.mjs +267 -0
  66. package/dist/lib/testing/storyboard/rejection-hints.js +13 -4
  67. package/dist/lib/testing/storyboard/rejection-hints.mjs +13 -4
  68. package/dist/lib/testing/storyboard/runner.d.mts +24 -0
  69. package/dist/lib/testing/storyboard/runner.d.ts +24 -0
  70. package/dist/lib/testing/storyboard/runner.js +1061 -105
  71. package/dist/lib/testing/storyboard/runner.mjs +1075 -106
  72. package/dist/lib/testing/storyboard/seeding.d.mts +15 -1
  73. package/dist/lib/testing/storyboard/seeding.d.ts +15 -1
  74. package/dist/lib/testing/storyboard/seeding.js +272 -12
  75. package/dist/lib/testing/storyboard/seeding.mjs +274 -13
  76. package/dist/lib/testing/storyboard/types.d.mts +207 -10
  77. package/dist/lib/testing/storyboard/types.d.ts +207 -10
  78. package/dist/lib/testing/storyboard/types.js +1 -1
  79. package/dist/lib/testing/storyboard/types.mjs +1 -1
  80. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  81. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  82. package/dist/lib/testing/storyboard/validations.js +95 -0
  83. package/dist/lib/testing/storyboard/validations.mjs +95 -0
  84. package/dist/lib/validation/bundled-schema-store.js +5 -0
  85. package/dist/lib/validation/bundled-schema-store.mjs +4 -0
  86. package/dist/lib/validation/schema-loader.js +19 -2
  87. package/dist/lib/validation/schema-loader.mjs +22 -3
  88. package/dist/lib/version.d.mts +3 -3
  89. package/dist/lib/version.d.ts +3 -3
  90. package/dist/lib/version.js +3 -3
  91. package/dist/lib/version.mjs +3 -3
  92. package/docs/TYPE-SUMMARY.md +1 -1
  93. package/docs/guides/COLLECTION-FIXTURE-FOUNDATION.md +850 -0
  94. package/docs/guides/PROPOSAL-TERMS-VERIFICATION.md +66 -0
  95. package/docs/llms.txt +3 -2
  96. package/docs/migration-14.x-rc-worksheet.md +4 -4
  97. package/package.json +1 -1
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Domain-qualified collection identity: one normalizer, one eligibility
3
+ * predicate, one witness builder, one set comparison.
4
+ *
5
+ * Every consumer of collection identity in the runner goes through this
6
+ * module — the fixture predicate (`collection_selection_satisfies`), the
7
+ * derived witness captures published by fixture preparation, and the
8
+ * `collection_set_equals_context` validator. Splitting those three would let
9
+ * "what counts as the same collection" drift between selection and grading,
10
+ * which is exactly the failure the upstream contract calls out.
11
+ *
12
+ * Identity rules, and the reasons they are what they are:
13
+ *
14
+ * - A selector is normalized to atomic `(publisher_domain, collection_id)`
15
+ * pairs. IDs are never flattened without their domain: two publishers may
16
+ * legitimately declare the same `collection_id`.
17
+ * - `collection_ids` is optional on `CollectionSelector` and means "every
18
+ * collection declared in that domain's adagents.json". That bulk-grant
19
+ * form is a *different* selector from an explicit enumeration, so it
20
+ * normalizes to a pair with `collection_id: null` rather than silently
21
+ * comparing equal to an enumerated set. Product composition is required
22
+ * to name explicit ids, so `null` pairs do not count toward
23
+ * `minimum_pairs`.
24
+ * - Schema-valid redundancy collapses: repeated ids inside one selector,
25
+ * and overlapping atomic pairs across differently-grouped selectors, are
26
+ * one pair. Neither `product.collections[]` nor `collection_ids[]`
27
+ * carries a uniqueness constraint, so redundancy is legal *product
28
+ * composition*. The readback arm is stricter:
29
+ * `core/collection-selection.json` declares `uniqueItems: true` on
30
+ * `collections` in `mode: selected`. Normalizing here is for COMPARISON
31
+ * only and never excuses that — the surrounding response is still
32
+ * schema-validated, which is what rejects a duplicated outer entry.
33
+ * Schema-invalid input (a non-array, a non-object entry, an empty
34
+ * `collection_ids`) stays a visible failure here and is additionally
35
+ * rejected by ordinary response-schema validation upstream.
36
+ * - Ordering is bytewise over UTF-8, on the *pairs*, so outer grouping and
37
+ * inner id order never affect the result while non-ASCII identifiers stay
38
+ * lossless. Nothing is case-folded, trimmed, punycoded or otherwise
39
+ * rewritten. `collection_id` is a plain string with no `minLength` and no
40
+ * declared normalization form, so inventing one would grade agents
41
+ * against a rule the protocol does not state. `publisher_domain` is
42
+ * separately pattern-constrained to lowercase LDH by
43
+ * `core/collection-selector.json`, which response-schema validation
44
+ * already enforces — canonicalizing it here would be redundant at best
45
+ * and would mask a non-conformant catalog at worst.
46
+ */
47
+ /** Maximum distinct pairs any single normalization will materialize. */
48
+ export declare const COLLECTION_PAIR_LIMIT = 10000;
49
+ /** Maximum pairs echoed into a diagnostic before it is elided. */
50
+ export declare const COLLECTION_DIAGNOSTIC_LIMIT = 20;
51
+ /** One atomic, domain-qualified collection identity. */
52
+ export interface CollectionPair {
53
+ publisher_domain: string;
54
+ /** `null` is the domain-only bulk-grant selector, not an empty id. */
55
+ collection_id: string | null;
56
+ }
57
+ /** A concrete `CollectionSelector` as it appears on the wire. */
58
+ export interface CollectionSelectorObject {
59
+ publisher_domain: string;
60
+ collection_ids?: string[];
61
+ }
62
+ export interface NormalizedCollectionSelectors {
63
+ /** Distinct pairs in bytewise order. */
64
+ pairs: CollectionPair[];
65
+ /** Outer selector entries observed, before normalization. */
66
+ selector_count: number;
67
+ /** Distinct publisher domains, bytewise ordered. */
68
+ domains: string[];
69
+ }
70
+ export type CollectionNormalizationCode = 'not_an_array' | 'selector_not_object' | 'publisher_domain_invalid' | 'collection_ids_invalid' | 'collection_id_invalid' | 'pair_limit_exceeded';
71
+ export interface CollectionNormalizationFailure {
72
+ code: CollectionNormalizationCode;
73
+ detail: string;
74
+ }
75
+ export type CollectionNormalizationResult = {
76
+ ok: true;
77
+ value: NormalizedCollectionSelectors;
78
+ } | {
79
+ ok: false;
80
+ failure: CollectionNormalizationFailure;
81
+ };
82
+ /** Total bytewise order over pairs. Domain first, then id; `null` sorts first. */
83
+ export declare function compareCollectionPairs(a: CollectionPair, b: CollectionPair): number;
84
+ /** Stable, unambiguous rendering for diagnostics and dedup keys. */
85
+ export declare function formatCollectionPair(pair: CollectionPair): string;
86
+ /**
87
+ * Normalize an array of `CollectionSelector` objects into distinct atomic
88
+ * pairs. Used for product catalog entries, authored expected sets and
89
+ * seller-returned `selected.collections` alike — same definition everywhere.
90
+ */
91
+ export declare function normalizeCollectionSelectors(value: unknown, options?: {
92
+ limit?: number;
93
+ }): CollectionNormalizationResult;
94
+ /** Exact set difference over normalized pairs, with bounded diagnostics. */
95
+ export declare function diffCollectionPairSets(actual: readonly CollectionPair[], expected: readonly CollectionPair[]): {
96
+ equal: boolean;
97
+ missing: CollectionPair[];
98
+ unexpected: CollectionPair[];
99
+ };
100
+ /** Render at most `COLLECTION_DIAGNOSTIC_LIMIT` pairs, flagging elision. */
101
+ export declare function summarizeCollectionPairs(pairs: readonly CollectionPair[]): string;
102
+ /**
103
+ * Operand of the `collection_selection_satisfies` fixture predicate.
104
+ *
105
+ * `collection_targeting` encodes the schema's omitted-false default
106
+ * explicitly rather than teaching every `equals` comparison to apply
107
+ * arbitrary defaults. It is deliberately NOT named `mode`: the wire's
108
+ * `collection_selection.mode` is `selected | default`, and the two appear in
109
+ * the same storyboard file.
110
+ *
111
+ * - `fixed` — `collection_targeting_allowed` is literal `false` OR
112
+ * absent (the normative default). `true` does not qualify.
113
+ * - `selectable` — literal `true` only.
114
+ * - `any` — either, as long as the flag is absent or a boolean.
115
+ *
116
+ * A non-boolean, non-absent flag never qualifies in any mode: that is a
117
+ * schema failure of the seller's catalog, surfaced by response-schema
118
+ * validation during discovery, not something this predicate repairs.
119
+ */
120
+ export interface CollectionSelectionSelector {
121
+ collection_targeting: 'selectable' | 'fixed' | 'any';
122
+ /**
123
+ * Distinct concrete pairs the product must expose. Defaults to `1`, which
124
+ * covers default materialization / full-bundle restatement; `2` is
125
+ * required before a genuine strict subset exists to exercise.
126
+ */
127
+ minimum_pairs?: 1 | 2;
128
+ /**
129
+ * Require a wire-visible owner/host relation: at least one collection
130
+ * selector whose publisher domain is outside every declared property
131
+ * (host) domain of the same product. Defaults to `false`.
132
+ */
133
+ require_cross_publisher_collection?: boolean;
134
+ }
135
+ /**
136
+ * Post-normalization operand the evaluator consumes, with both defaults
137
+ * applied.
138
+ *
139
+ * Kept separate from the authored form so the type accepts exactly what the
140
+ * loader accepts: `{ collection_targeting: 'fixed' }` is valid YAML and must type-check,
141
+ * while an index signature would have let a typo'd key compile clean and
142
+ * then fail at load against the closed-operand rule.
143
+ */
144
+ export interface ResolvedCollectionSelectionSelector {
145
+ collection_targeting: 'selectable' | 'fixed' | 'any';
146
+ minimum_pairs: 1 | 2;
147
+ require_cross_publisher_collection: boolean;
148
+ }
149
+ export declare const COLLECTION_TARGETING_MODES: readonly ["selectable", "fixed", "any"];
150
+ /** Validate and default the closed operand. Throws on authoring errors. */
151
+ export declare function normalizeCollectionSelectionSelector(value: unknown, location: string): ResolvedCollectionSelectionSelector;
152
+ /** The declared owner/host relation observed on one product. */
153
+ export interface CrossPublisherCollectionRelation {
154
+ /**
155
+ * The collection namespace that lies outside every property namespace of
156
+ * the SAME selected product.
157
+ */
158
+ collection_publisher_domain: string;
159
+ /** Property namespaces declared by that same product. */
160
+ property_publisher_domains: string[];
161
+ /**
162
+ * The concrete selector carrying it. `selector.publisher_domain` always
163
+ * equals `collection_publisher_domain`.
164
+ */
165
+ selector: CollectionSelectorObject;
166
+ }
167
+ /**
168
+ * Everything the resolver retains about the product it actually selected.
169
+ * Built once, from the selected candidate, and never recomputed from a
170
+ * different response.
171
+ */
172
+ export interface CollectionSelectionWitness {
173
+ product_id: string | null;
174
+ /**
175
+ * Effective targeting of the selected product, with the schema's
176
+ * omitted-false default applied. Distinct from the native seller field
177
+ * `collection_targeting_allowed`, whose raw observed value is reported
178
+ * beside it, and from the wire's `collection_selection.mode`.
179
+ */
180
+ collection_targeting: 'selectable' | 'fixed';
181
+ /** The flag exactly as observed — `undefined` when the seller omitted it. */
182
+ collection_targeting_allowed: boolean | undefined;
183
+ /** The product's complete, concrete collection selector bundle. */
184
+ declared_selectors: CollectionSelectorObject[];
185
+ /** Distinct concrete pairs (bulk-grant entries excluded). */
186
+ distinct_pairs: CollectionPair[];
187
+ /**
188
+ * A one-entry selector array naming exactly one of the bundle's pairs, so
189
+ * it is a strict subset whenever the bundle has two or more. Shaped as an
190
+ * array because that is the shape a `collections` field takes on the wire,
191
+ * so a storyboard can substitute it whole.
192
+ *
193
+ * When `require_cross_publisher_collection` was requested, the pair is drawn from the
194
+ * retained external owner, so the subset actually exercises the carriage
195
+ * relation rather than the host's own inventory.
196
+ *
197
+ * `null` when the product exposes fewer than two distinct concrete pairs —
198
+ * a one-pair product still qualifies for default materialization, so the
199
+ * absence of a subset witness must not suppress that coverage.
200
+ */
201
+ strict_subset: CollectionSelectorObject[] | null;
202
+ /**
203
+ * One-entry selector array naming an id verified absent from this
204
+ * product's pairs, under a domain the product does carry.
205
+ */
206
+ undeclared_collection_id_selectors: CollectionSelectorObject[];
207
+ /**
208
+ * One-entry selector array whose domain is verified absent from every
209
+ * COLLECTION namespace this product declares.
210
+ *
211
+ * Property namespaces (`publisher_properties[].publisher_domain`) are not
212
+ * checked — the synthesized domain is a `.invalid` name, which RFC 2606
213
+ * reserves and no real publisher can hold, so a collision is not possible
214
+ * in practice. Stated precisely because "every namespace this product
215
+ * declares" read as including the property side, and it does not.
216
+ */
217
+ undeclared_publisher_domain_selectors: CollectionSelectorObject[];
218
+ /** Present only when the relation was requested and found. */
219
+ cross_publisher_relation: CrossPublisherCollectionRelation | null;
220
+ /** Declared property namespaces, bytewise ordered. */
221
+ property_publisher_domains: string[];
222
+ }
223
+ export type CollectionSelectionEvaluation = {
224
+ eligible: true;
225
+ witness: CollectionSelectionWitness;
226
+ } | {
227
+ eligible: false;
228
+ reason: string;
229
+ };
230
+ /**
231
+ * Decide whether one discovered product qualifies, and retain the witness
232
+ * for the candidate that is actually selected.
233
+ *
234
+ * Qualification is evaluated against the complete validated candidate before
235
+ * any deterministic ordering is applied, so a nonqualifying first product can
236
+ * never hide a later qualifying one.
237
+ */
238
+ export declare function evaluateCollectionSelection(candidate: unknown, selector: CollectionSelectionSelector): CollectionSelectionEvaluation;
239
+ /** Boolean form for the legacy matcher. */
240
+ export declare function productSatisfiesCollectionSelection(candidate: unknown, selector: CollectionSelectionSelector): boolean;
241
+ /** Derived witness captures a declaration may bind to a context key. */
242
+ export declare const COLLECTION_WITNESS_KINDS: readonly ["declared_selectors", "distinct_pairs", "strict_subset", "undeclared_collection_id_selectors", "undeclared_publisher_domain_selectors", "cross_publisher_relation", "property_publisher_domains"];
243
+ export type CollectionWitnessKind = (typeof COLLECTION_WITNESS_KINDS)[number];
244
+ export type CollectionWitnessReadResult = {
245
+ ok: true;
246
+ value: unknown;
247
+ } | {
248
+ ok: false;
249
+ detail: string;
250
+ };
251
+ /**
252
+ * Read one derived capture off a retained witness.
253
+ *
254
+ * A declared capture that cannot be built is a setup failure, never an
255
+ * unsupported/not-applicable downgrade — the storyboard asked for state the
256
+ * selected product does not provide, and silently proceeding would grade the
257
+ * agent against a request the author did not write.
258
+ */
259
+ export declare function readCollectionWitness(witness: CollectionSelectionWitness | undefined, kind: CollectionWitnessKind): CollectionWitnessReadResult;
@@ -0,0 +1,384 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var collection_selection_exports = {};
20
+ __export(collection_selection_exports, {
21
+ COLLECTION_DIAGNOSTIC_LIMIT: () => COLLECTION_DIAGNOSTIC_LIMIT,
22
+ COLLECTION_PAIR_LIMIT: () => COLLECTION_PAIR_LIMIT,
23
+ COLLECTION_TARGETING_MODES: () => COLLECTION_TARGETING_MODES,
24
+ COLLECTION_WITNESS_KINDS: () => COLLECTION_WITNESS_KINDS,
25
+ compareCollectionPairs: () => compareCollectionPairs,
26
+ diffCollectionPairSets: () => diffCollectionPairSets,
27
+ evaluateCollectionSelection: () => evaluateCollectionSelection,
28
+ formatCollectionPair: () => formatCollectionPair,
29
+ normalizeCollectionSelectionSelector: () => normalizeCollectionSelectionSelector,
30
+ normalizeCollectionSelectors: () => normalizeCollectionSelectors,
31
+ productSatisfiesCollectionSelection: () => productSatisfiesCollectionSelection,
32
+ readCollectionWitness: () => readCollectionWitness,
33
+ summarizeCollectionPairs: () => summarizeCollectionPairs
34
+ });
35
+ module.exports = __toCommonJS(collection_selection_exports);
36
+ const PAIR_LIMIT_DEFAULT = 1e4;
37
+ const COLLECTION_PAIR_LIMIT = PAIR_LIMIT_DEFAULT;
38
+ const COLLECTION_DIAGNOSTIC_LIMIT = 20;
39
+ function isRecord(value) {
40
+ return value !== null && typeof value === "object" && !Array.isArray(value);
41
+ }
42
+ function utf8Compare(a, b) {
43
+ return Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
44
+ }
45
+ function compareCollectionPairs(a, b) {
46
+ const domain = utf8Compare(a.publisher_domain, b.publisher_domain);
47
+ if (domain !== 0) return domain;
48
+ if (a.collection_id === b.collection_id) return 0;
49
+ if (a.collection_id === null) return -1;
50
+ if (b.collection_id === null) return 1;
51
+ return utf8Compare(a.collection_id, b.collection_id);
52
+ }
53
+ function formatCollectionPair(pair) {
54
+ return pair.collection_id === null ? `${JSON.stringify(pair.publisher_domain)}:*` : `${JSON.stringify(pair.publisher_domain)}:${JSON.stringify(pair.collection_id)}`;
55
+ }
56
+ function pairKey(pair) {
57
+ return pair.collection_id === null ? `${JSON.stringify(pair.publisher_domain)}\0` : `${JSON.stringify(pair.publisher_domain)}\0${JSON.stringify(pair.collection_id)}`;
58
+ }
59
+ function normalizeCollectionSelectors(value, options = {}) {
60
+ const limit = options.limit ?? PAIR_LIMIT_DEFAULT;
61
+ if (!Array.isArray(value)) {
62
+ return {
63
+ ok: false,
64
+ failure: {
65
+ code: "not_an_array",
66
+ detail: `expected an array of collection selectors; received ${describe(value)}`
67
+ }
68
+ };
69
+ }
70
+ const seen = /* @__PURE__ */ new Map();
71
+ for (let index = 0; index < value.length; index++) {
72
+ const selector = value[index];
73
+ if (!isRecord(selector)) {
74
+ return {
75
+ ok: false,
76
+ failure: {
77
+ code: "selector_not_object",
78
+ detail: `collections[${index}] must be an object; received ${describe(selector)}`
79
+ }
80
+ };
81
+ }
82
+ const domain = selector.publisher_domain;
83
+ if (typeof domain !== "string" || domain.length === 0) {
84
+ return {
85
+ ok: false,
86
+ failure: {
87
+ code: "publisher_domain_invalid",
88
+ detail: `collections[${index}].publisher_domain must be a non-empty string; received ${describe(domain)}`
89
+ }
90
+ };
91
+ }
92
+ const ids = selector.collection_ids;
93
+ if (ids === void 0 || ids === null) {
94
+ addPair(seen, { publisher_domain: domain, collection_id: null });
95
+ } else {
96
+ if (!Array.isArray(ids) || ids.length === 0) {
97
+ return {
98
+ ok: false,
99
+ failure: {
100
+ code: "collection_ids_invalid",
101
+ detail: `collections[${index}].collection_ids must be a non-empty array when present; received ${describe(ids)}`
102
+ }
103
+ };
104
+ }
105
+ for (let idIndex = 0; idIndex < ids.length; idIndex++) {
106
+ const id = ids[idIndex];
107
+ if (typeof id !== "string") {
108
+ return {
109
+ ok: false,
110
+ failure: {
111
+ code: "collection_id_invalid",
112
+ detail: `collections[${index}].collection_ids[${idIndex}] must be a string; received ${describe(id)}`
113
+ }
114
+ };
115
+ }
116
+ addPair(seen, { publisher_domain: domain, collection_id: id });
117
+ if (seen.size > limit) return pairLimitExceeded(limit, index);
118
+ }
119
+ }
120
+ if (seen.size > limit) return pairLimitExceeded(limit, index);
121
+ }
122
+ const pairs = [...seen.values()].sort(compareCollectionPairs);
123
+ const domains = [...new Set(pairs.map((pair) => pair.publisher_domain))].sort(utf8Compare);
124
+ return { ok: true, value: { pairs, selector_count: value.length, domains } };
125
+ }
126
+ function pairLimitExceeded(limit, index) {
127
+ return {
128
+ ok: false,
129
+ failure: {
130
+ code: "pair_limit_exceeded",
131
+ detail: `normalized collection pairs exceeded the ${limit}-pair safety limit at collections[${index}]`
132
+ }
133
+ };
134
+ }
135
+ function addPair(seen, pair) {
136
+ const key = pairKey(pair);
137
+ if (!seen.has(key)) seen.set(key, pair);
138
+ }
139
+ function describe(value) {
140
+ if (value === void 0) return "undefined";
141
+ if (value === null) return "null";
142
+ if (Array.isArray(value)) return `an array of length ${value.length}`;
143
+ return typeof value === "object" ? "an object" : JSON.stringify(value);
144
+ }
145
+ function diffCollectionPairSets(actual, expected) {
146
+ const expectedKeys = new Set(expected.map(pairKey));
147
+ const actualKeys = new Set(actual.map(pairKey));
148
+ const missing = expected.filter((pair) => !actualKeys.has(pairKey(pair)));
149
+ const unexpected = actual.filter((pair) => !expectedKeys.has(pairKey(pair)));
150
+ return { equal: missing.length === 0 && unexpected.length === 0, missing, unexpected };
151
+ }
152
+ function summarizeCollectionPairs(pairs) {
153
+ const shown = pairs.slice(0, COLLECTION_DIAGNOSTIC_LIMIT).map(formatCollectionPair);
154
+ const elided = pairs.length - shown.length;
155
+ return elided > 0 ? `${shown.join(", ")} (+${elided} more)` : shown.join(", ");
156
+ }
157
+ const COLLECTION_TARGETING_MODES = ["selectable", "fixed", "any"];
158
+ function normalizeCollectionSelectionSelector(value, location) {
159
+ if (!isRecord(value)) throw new Error(`${location}: must be an object`);
160
+ const unknown = Object.keys(value).filter(
161
+ (key) => !["collection_targeting", "minimum_pairs", "require_cross_publisher_collection"].includes(key)
162
+ );
163
+ if (unknown.length > 0) {
164
+ throw new Error(`${location}: unknown key(s): ${unknown.join(", ")}; the operand is closed`);
165
+ }
166
+ const targeting = value.collection_targeting;
167
+ if (typeof targeting !== "string" || !COLLECTION_TARGETING_MODES.includes(targeting)) {
168
+ throw new Error(`${location}.collection_targeting: must be one of ${COLLECTION_TARGETING_MODES.join(", ")}`);
169
+ }
170
+ let minimumPairs = 1;
171
+ if (value.minimum_pairs !== void 0) {
172
+ if (value.minimum_pairs !== 1 && value.minimum_pairs !== 2) {
173
+ throw new Error(`${location}.minimum_pairs: must be 1 or 2`);
174
+ }
175
+ minimumPairs = value.minimum_pairs;
176
+ }
177
+ if (value.require_cross_publisher_collection !== void 0 && typeof value.require_cross_publisher_collection !== "boolean") {
178
+ throw new Error(`${location}.require_cross_publisher_collection: must be boolean`);
179
+ }
180
+ return {
181
+ collection_targeting: targeting,
182
+ minimum_pairs: minimumPairs,
183
+ require_cross_publisher_collection: value.require_cross_publisher_collection === true
184
+ };
185
+ }
186
+ function collectHostDomains(candidate) {
187
+ const domains = /* @__PURE__ */ new Set();
188
+ const properties = candidate.publisher_properties;
189
+ if (Array.isArray(properties)) {
190
+ for (const entry of properties) {
191
+ if (!isRecord(entry)) continue;
192
+ if (typeof entry.publisher_domain === "string" && entry.publisher_domain.length > 0) {
193
+ domains.add(entry.publisher_domain);
194
+ }
195
+ }
196
+ }
197
+ return [...domains].sort(utf8Compare);
198
+ }
199
+ function cloneSelectorBundle(value) {
200
+ return value.map((selector) => ({
201
+ publisher_domain: selector.publisher_domain,
202
+ ...Array.isArray(selector.collection_ids) && { collection_ids: [...selector.collection_ids] }
203
+ }));
204
+ }
205
+ function buildUndeclaredCollectionIdSelectors(domain, pairs) {
206
+ const taken = new Set(pairs.filter((pair) => pair.publisher_domain === domain).map((pair) => pair.collection_id));
207
+ let candidate = "adcp-absent-collection";
208
+ for (let suffix = 0; taken.has(candidate); suffix++) {
209
+ candidate = `adcp-absent-collection-${suffix}`;
210
+ }
211
+ return [{ publisher_domain: domain, collection_ids: [candidate] }];
212
+ }
213
+ function buildUndeclaredPublisherDomainSelectors(domains, collectionId) {
214
+ let candidate = "adcp-absent-owner.invalid";
215
+ for (let suffix = 0; domains.has(candidate); suffix++) {
216
+ candidate = `adcp-absent-owner-${suffix}.invalid`;
217
+ }
218
+ return [{ publisher_domain: candidate, collection_ids: [collectionId] }];
219
+ }
220
+ function evaluateCollectionSelection(candidate, selector) {
221
+ if (selector !== null && typeof selector === "object" && "declared_selectors" in selector) {
222
+ throw new Error(
223
+ '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 }.'
224
+ );
225
+ }
226
+ const resolved = {
227
+ collection_targeting: selector.collection_targeting,
228
+ minimum_pairs: selector.minimum_pairs ?? 1,
229
+ require_cross_publisher_collection: selector.require_cross_publisher_collection === true
230
+ };
231
+ return evaluateResolvedCollectionSelection(candidate, resolved);
232
+ }
233
+ function evaluateResolvedCollectionSelection(candidate, selector) {
234
+ if (!isRecord(candidate)) return { eligible: false, reason: "candidate is not an object" };
235
+ const flag = candidate.collection_targeting_allowed;
236
+ if (flag !== void 0 && typeof flag !== "boolean") {
237
+ return {
238
+ eligible: false,
239
+ reason: `collection_targeting_allowed must be boolean or absent; observed ${describe(flag)}`
240
+ };
241
+ }
242
+ const effectiveTargeting = flag === true ? "selectable" : "fixed";
243
+ if (selector.collection_targeting !== "any" && selector.collection_targeting !== effectiveTargeting) {
244
+ return {
245
+ eligible: false,
246
+ 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"
247
+ };
248
+ }
249
+ const normalized = normalizeCollectionSelectors(candidate.collections);
250
+ if (!normalized.ok) {
251
+ return { eligible: false, reason: `collections did not normalize: ${normalized.failure.detail}` };
252
+ }
253
+ if (normalized.value.pairs.some((pair) => pair.collection_id === null)) {
254
+ return {
255
+ eligible: false,
256
+ reason: "product collections contain a domain-only bulk-grant selector; product composition requires explicit collection_ids on every entry"
257
+ };
258
+ }
259
+ const concrete = normalized.value.pairs;
260
+ if (concrete.length < selector.minimum_pairs) {
261
+ return {
262
+ eligible: false,
263
+ reason: `product exposes ${concrete.length} distinct concrete collection pair(s); ${selector.minimum_pairs} required`
264
+ };
265
+ }
266
+ const hostDomains = collectHostDomains(candidate);
267
+ const bundle = cloneSelectorBundle(candidate.collections);
268
+ let relation = null;
269
+ if (selector.require_cross_publisher_collection) {
270
+ const hostSet = new Set(hostDomains);
271
+ const externalDomain = normalized.value.domains.find((domain) => !hostSet.has(domain));
272
+ if (externalDomain === void 0) {
273
+ return {
274
+ eligible: false,
275
+ 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"
276
+ };
277
+ }
278
+ const carrying = bundle.filter((entry) => entry.publisher_domain === externalDomain).sort((left, right) => {
279
+ const l = JSON.stringify([...left.collection_ids ?? []].sort());
280
+ const r = JSON.stringify([...right.collection_ids ?? []].sort());
281
+ return l < r ? -1 : l > r ? 1 : 0;
282
+ })[0];
283
+ relation = {
284
+ collection_publisher_domain: externalDomain,
285
+ property_publisher_domains: hostDomains,
286
+ selector: {
287
+ ...carrying,
288
+ ...carrying.collection_ids && { collection_ids: [...carrying.collection_ids] }
289
+ }
290
+ };
291
+ }
292
+ const subsetSource = relation === null ? concrete[0] : (
293
+ // Non-null is sound, not optimism: `relation.collection_publisher_domain`
294
+ // is taken from `normalized.value.domains`, which is derived from
295
+ // `concrete`, so the domain is present by construction. The previous
296
+ // `?? concrete[0]` read as a carriage-vs-local fallback and was not
297
+ // one — it could only ever have masked a normalizer bug.
298
+ concrete.find((pair) => pair.publisher_domain === relation.collection_publisher_domain)
299
+ );
300
+ const strictSubset = concrete.length >= 2 ? [{ publisher_domain: subsetSource.publisher_domain, collection_ids: [subsetSource.collection_id] }] : null;
301
+ const witness = {
302
+ product_id: typeof candidate.product_id === "string" ? candidate.product_id : null,
303
+ collection_targeting: effectiveTargeting,
304
+ collection_targeting_allowed: flag,
305
+ declared_selectors: bundle,
306
+ distinct_pairs: concrete,
307
+ strict_subset: strictSubset,
308
+ undeclared_collection_id_selectors: buildUndeclaredCollectionIdSelectors(
309
+ subsetSource.publisher_domain,
310
+ normalized.value.pairs
311
+ ),
312
+ undeclared_publisher_domain_selectors: buildUndeclaredPublisherDomainSelectors(
313
+ new Set(normalized.value.domains),
314
+ subsetSource.collection_id
315
+ ),
316
+ cross_publisher_relation: relation,
317
+ property_publisher_domains: hostDomains
318
+ };
319
+ return { eligible: true, witness };
320
+ }
321
+ function productSatisfiesCollectionSelection(candidate, selector) {
322
+ return evaluateCollectionSelection(candidate, selector).eligible;
323
+ }
324
+ const COLLECTION_WITNESS_KINDS = [
325
+ "declared_selectors",
326
+ "distinct_pairs",
327
+ "strict_subset",
328
+ "undeclared_collection_id_selectors",
329
+ "undeclared_publisher_domain_selectors",
330
+ "cross_publisher_relation",
331
+ "property_publisher_domains"
332
+ ];
333
+ function readCollectionWitness(witness, kind) {
334
+ if (!witness) {
335
+ return {
336
+ ok: false,
337
+ detail: `witness capture "${kind}" requires a collection_selection_satisfies predicate on the same declaration`
338
+ };
339
+ }
340
+ switch (kind) {
341
+ case "declared_selectors":
342
+ return { ok: true, value: witness.declared_selectors };
343
+ case "distinct_pairs":
344
+ return { ok: true, value: witness.distinct_pairs };
345
+ case "strict_subset":
346
+ if (witness.strict_subset === null) {
347
+ return {
348
+ ok: false,
349
+ detail: "strict_subset requires a product with at least two distinct concrete collection pairs; declare minimum_pairs: 2"
350
+ };
351
+ }
352
+ return { ok: true, value: witness.strict_subset };
353
+ case "undeclared_collection_id_selectors":
354
+ return { ok: true, value: witness.undeclared_collection_id_selectors };
355
+ case "undeclared_publisher_domain_selectors":
356
+ return { ok: true, value: witness.undeclared_publisher_domain_selectors };
357
+ case "cross_publisher_relation":
358
+ if (witness.cross_publisher_relation === null) {
359
+ return {
360
+ ok: false,
361
+ detail: "cross_publisher_relation requires require_cross_publisher_collection: true on the collection_selection_satisfies operand"
362
+ };
363
+ }
364
+ return { ok: true, value: witness.cross_publisher_relation };
365
+ case "property_publisher_domains":
366
+ return { ok: true, value: witness.property_publisher_domains };
367
+ }
368
+ }
369
+ // Annotate the CommonJS export names for ESM import in node:
370
+ 0 && (module.exports = {
371
+ COLLECTION_DIAGNOSTIC_LIMIT,
372
+ COLLECTION_PAIR_LIMIT,
373
+ COLLECTION_TARGETING_MODES,
374
+ COLLECTION_WITNESS_KINDS,
375
+ compareCollectionPairs,
376
+ diffCollectionPairSets,
377
+ evaluateCollectionSelection,
378
+ formatCollectionPair,
379
+ normalizeCollectionSelectionSelector,
380
+ normalizeCollectionSelectors,
381
+ productSatisfiesCollectionSelection,
382
+ readCollectionWitness,
383
+ summarizeCollectionPairs
384
+ });