@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.
- package/AGENTS.md +2 -0
- package/dist/lib/core/AgentClient.js +17 -1
- package/dist/lib/core/AgentClient.mjs +17 -1
- package/dist/lib/core/SingleAgentClient.js +13 -0
- package/dist/lib/core/SingleAgentClient.mjs +13 -0
- package/dist/lib/core/TaskExecutor.js +88 -13
- package/dist/lib/core/TaskExecutor.mjs +89 -14
- package/dist/lib/errors/index.d.mts +34 -0
- package/dist/lib/errors/index.d.ts +34 -0
- package/dist/lib/errors/index.js +11 -0
- package/dist/lib/errors/index.mjs +10 -0
- package/dist/lib/index.d.mts +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.mjs +2 -0
- package/dist/lib/media-buy/compatibility.d.mts +1 -0
- package/dist/lib/media-buy/compatibility.d.ts +1 -0
- package/dist/lib/media-buy/compatibility.js +33 -10
- package/dist/lib/media-buy/compatibility.mjs +32 -14
- package/dist/lib/negotiation/buyer.js +11 -5
- package/dist/lib/negotiation/buyer.mjs +11 -5
- package/dist/lib/negotiation/types.d.mts +4 -2
- package/dist/lib/negotiation/types.d.ts +4 -2
- package/dist/lib/negotiation/verification.d.mts +15 -6
- package/dist/lib/negotiation/verification.d.ts +15 -6
- package/dist/lib/negotiation/verification.js +146 -27
- package/dist/lib/negotiation/verification.mjs +146 -28
- package/dist/lib/negotiation/version.d.mts +6 -0
- package/dist/lib/negotiation/version.d.ts +6 -0
- package/dist/lib/negotiation/version.js +60 -0
- package/dist/lib/negotiation/version.mjs +33 -0
- package/dist/lib/server/create-adcp-server.js +1 -1
- package/dist/lib/server/create-adcp-server.mjs +1 -1
- package/dist/lib/server/error-arm-tools.d.mts +10 -10
- package/dist/lib/server/error-arm-tools.d.ts +10 -10
- package/dist/lib/server/error-arm-tools.js +22 -58
- package/dist/lib/server/error-arm-tools.mjs +30 -65
- package/dist/lib/testing/compliance/comply.js +17 -0
- package/dist/lib/testing/compliance/comply.mjs +17 -0
- package/dist/lib/testing/index.d.mts +3 -0
- package/dist/lib/testing/index.d.ts +3 -0
- package/dist/lib/testing/index.js +44 -2
- package/dist/lib/testing/index.mjs +42 -0
- package/dist/lib/testing/storyboard/collection-selection.d.mts +259 -0
- package/dist/lib/testing/storyboard/collection-selection.d.ts +259 -0
- package/dist/lib/testing/storyboard/collection-selection.js +384 -0
- package/dist/lib/testing/storyboard/collection-selection.mjs +348 -0
- package/dist/lib/testing/storyboard/context.js +12 -6
- package/dist/lib/testing/storyboard/context.mjs +12 -6
- package/dist/lib/testing/storyboard/fixture-resolution.d.mts +39 -1
- package/dist/lib/testing/storyboard/fixture-resolution.d.ts +39 -1
- package/dist/lib/testing/storyboard/fixture-resolution.js +216 -12
- package/dist/lib/testing/storyboard/fixture-resolution.mjs +218 -12
- package/dist/lib/testing/storyboard/index.d.mts +9 -2
- package/dist/lib/testing/storyboard/index.d.ts +9 -2
- package/dist/lib/testing/storyboard/index.js +51 -2
- package/dist/lib/testing/storyboard/index.mjs +49 -1
- package/dist/lib/testing/storyboard/prepared-fixtures.d.mts +471 -0
- package/dist/lib/testing/storyboard/prepared-fixtures.d.ts +471 -0
- package/dist/lib/testing/storyboard/prepared-fixtures.js +434 -0
- package/dist/lib/testing/storyboard/prepared-fixtures.mjs +389 -0
- package/dist/lib/testing/storyboard/protected-context.d.mts +127 -0
- package/dist/lib/testing/storyboard/protected-context.d.ts +127 -0
- package/dist/lib/testing/storyboard/protected-context.js +301 -0
- package/dist/lib/testing/storyboard/protected-context.mjs +267 -0
- package/dist/lib/testing/storyboard/rejection-hints.js +13 -4
- package/dist/lib/testing/storyboard/rejection-hints.mjs +13 -4
- package/dist/lib/testing/storyboard/runner.d.mts +24 -0
- package/dist/lib/testing/storyboard/runner.d.ts +24 -0
- package/dist/lib/testing/storyboard/runner.js +1061 -105
- package/dist/lib/testing/storyboard/runner.mjs +1075 -106
- package/dist/lib/testing/storyboard/seeding.d.mts +15 -1
- package/dist/lib/testing/storyboard/seeding.d.ts +15 -1
- package/dist/lib/testing/storyboard/seeding.js +272 -12
- package/dist/lib/testing/storyboard/seeding.mjs +274 -13
- package/dist/lib/testing/storyboard/types.d.mts +207 -10
- package/dist/lib/testing/storyboard/types.d.ts +207 -10
- package/dist/lib/testing/storyboard/types.js +1 -1
- package/dist/lib/testing/storyboard/types.mjs +1 -1
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/testing/storyboard/validations.js +95 -0
- package/dist/lib/testing/storyboard/validations.mjs +95 -0
- package/dist/lib/validation/bundled-schema-store.js +5 -0
- package/dist/lib/validation/bundled-schema-store.mjs +4 -0
- package/dist/lib/validation/schema-loader.js +19 -2
- package/dist/lib/validation/schema-loader.mjs +22 -3
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +1 -1
- package/docs/guides/COLLECTION-FIXTURE-FOUNDATION.md +850 -0
- package/docs/guides/PROPOSAL-TERMS-VERIFICATION.md +66 -0
- package/docs/llms.txt +3 -2
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- 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
|
+
});
|