@adcp/sdk 16.1.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/governance/authorization.d.mts +66 -0
- package/dist/lib/governance/authorization.d.ts +66 -0
- package/dist/lib/governance/authorization.js +181 -37
- package/dist/lib/governance/authorization.mjs +183 -36
- package/dist/lib/governance/index.d.mts +2 -2
- package/dist/lib/governance/index.d.ts +2 -2
- package/dist/lib/governance/index.js +4 -2
- package/dist/lib/governance/index.mjs +3 -1
- package/dist/lib/identity-3.3/index.d.mts +1 -0
- package/dist/lib/identity-3.3/index.d.ts +1 -0
- package/dist/lib/identity-3.3/index.js +3 -1
- package/dist/lib/identity-3.3/index.mjs +1 -0
- package/dist/lib/identity-3.3/jws-artifacts.d.mts +147 -0
- package/dist/lib/identity-3.3/jws-artifacts.d.ts +147 -0
- package/dist/lib/identity-3.3/jws-artifacts.js +258 -0
- package/dist/lib/identity-3.3/jws-artifacts.mjs +234 -0
- package/dist/lib/identity-3.3/revocation.d.mts +7 -0
- package/dist/lib/identity-3.3/revocation.d.ts +7 -0
- package/dist/lib/identity-3.3/revocation.js +13 -0
- package/dist/lib/identity-3.3/revocation.mjs +13 -0
- package/dist/lib/index.d.mts +3 -3
- package/dist/lib/index.d.ts +3 -3
- package/dist/lib/index.js +4 -0
- package/dist/lib/index.mjs +4 -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/registry/types.generated.d.mts +24 -1
- package/dist/lib/registry/types.generated.d.ts +24 -1
- 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/server/index.d.mts +2 -2
- package/dist/lib/server/index.d.ts +2 -2
- package/dist/lib/server/index.js +2 -0
- package/dist/lib/server/index.mjs +2 -0
- package/dist/lib/signing/web-bot-auth.js +5 -0
- package/dist/lib/signing/web-bot-auth.mjs +4 -0
- 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 +2 -2
- package/docs/guides/COLLECTION-FIXTURE-FOUNDATION.md +850 -0
- package/docs/guides/PROPOSAL-TERMS-VERIFICATION.md +66 -0
- package/docs/llms.txt +4 -3
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/docs/migration-agent-identity-3.3.md +51 -6
- package/package.json +6 -4
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
562
|
-
(0, import_path.setPath)(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
529
|
-
setPath(
|
|
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
|
|
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
|
|
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[];
|