@adcp/sdk 14.0.0-rc.39 → 14.0.0-rc.40
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/README.md +2 -0
- package/dist/lib/core/AgentClient.d.mts +4 -3
- package/dist/lib/core/AgentClient.d.ts +4 -3
- package/dist/lib/core/AsyncHandler.d.mts +3 -0
- package/dist/lib/core/AsyncHandler.d.ts +3 -0
- package/dist/lib/core/AsyncHandler.js +3 -0
- package/dist/lib/core/AsyncHandler.mjs +3 -0
- package/dist/lib/core/SingleAgentClient.d.mts +6 -1
- package/dist/lib/core/SingleAgentClient.d.ts +6 -1
- package/dist/lib/core/SingleAgentClient.js +57 -17
- package/dist/lib/core/SingleAgentClient.mjs +59 -17
- package/dist/lib/discovery/adagents-redirects.d.mts +5 -1
- package/dist/lib/discovery/adagents-redirects.d.ts +5 -1
- package/dist/lib/discovery/adagents-redirects.js +10 -3
- package/dist/lib/discovery/adagents-redirects.mjs +10 -3
- package/dist/lib/discovery/types.d.mts +42 -0
- package/dist/lib/discovery/types.d.ts +42 -0
- package/dist/lib/index.d.mts +3 -1
- package/dist/lib/index.d.ts +3 -1
- package/dist/lib/index.js +3 -1
- package/dist/lib/index.mjs +1 -0
- package/dist/lib/net/ssrf-fetch.js +47 -21
- package/dist/lib/net/ssrf-fetch.mjs +47 -21
- package/dist/lib/registry/index.d.mts +3 -0
- package/dist/lib/registry/index.d.ts +3 -0
- package/dist/lib/registry/index.js +8 -0
- package/dist/lib/registry/index.mjs +8 -0
- package/dist/lib/registry/types.d.mts +2 -1
- package/dist/lib/registry/types.d.ts +2 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/supply-path/evaluate.d.mts +17 -0
- package/dist/lib/supply-path/evaluate.d.ts +17 -0
- package/dist/lib/supply-path/evaluate.js +436 -0
- package/dist/lib/supply-path/evaluate.mjs +407 -0
- package/dist/lib/supply-path/fetch-evidence.d.mts +36 -0
- package/dist/lib/supply-path/fetch-evidence.d.ts +36 -0
- package/dist/lib/supply-path/fetch-evidence.js +293 -0
- package/dist/lib/supply-path/fetch-evidence.mjs +268 -0
- package/dist/lib/supply-path/index.d.mts +6 -0
- package/dist/lib/supply-path/index.d.ts +6 -0
- package/dist/lib/supply-path/index.js +49 -0
- package/dist/lib/supply-path/index.mjs +23 -0
- package/dist/lib/supply-path/products.d.mts +41 -0
- package/dist/lib/supply-path/products.d.ts +41 -0
- package/dist/lib/supply-path/products.js +228 -0
- package/dist/lib/supply-path/products.mjs +204 -0
- package/dist/lib/supply-path/revocations.d.mts +55 -0
- package/dist/lib/supply-path/revocations.d.ts +55 -0
- package/dist/lib/supply-path/revocations.js +127 -0
- package/dist/lib/supply-path/revocations.mjs +99 -0
- package/dist/lib/supply-path/types.d.mts +168 -0
- package/dist/lib/supply-path/types.d.ts +168 -0
- package/dist/lib/supply-path/types.js +16 -0
- package/dist/lib/supply-path/types.mjs +0 -0
- package/dist/lib/supply-path/validation.d.mts +10 -0
- package/dist/lib/supply-path/validation.d.ts +10 -0
- package/dist/lib/supply-path/validation.js +152 -0
- package/dist/lib/supply-path/validation.mjs +122 -0
- package/dist/lib/supply-path/verify.d.mts +25 -0
- package/dist/lib/supply-path/verify.d.ts +25 -0
- package/dist/lib/supply-path/verify.js +218 -0
- package/dist/lib/supply-path/verify.mjs +203 -0
- package/dist/lib/testing/storyboard/request-signing/grader.js +19 -9
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +19 -9
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/v2/projection/creative-delivery.d.mts +1 -1
- package/dist/lib/v2/projection/creative-delivery.d.ts +1 -1
- package/dist/lib/validation/schema-loader.js +7 -5
- package/dist/lib/validation/schema-loader.mjs +7 -5
- 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/SUPPLY-PATH-VERIFICATION.md +101 -0
- package/docs/llms.txt +2 -2
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/examples/README.md +2 -0
- package/examples/supply-path-verification.ts +29 -0
- package/package.json +2 -1
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { withAbortSignal } from "../protocols/abort.mjs";
|
|
2
|
+
import {
|
|
3
|
+
parsePublisherPropertySelector,
|
|
4
|
+
expandPublisherPropertySelector
|
|
5
|
+
} from "../discovery/publisher-property-selector.mjs";
|
|
6
|
+
import { domain, records, strings } from "./validation.mjs";
|
|
7
|
+
import { RegistryClient } from "../registry/index.mjs";
|
|
8
|
+
import { SupplyPathEvidenceSession } from "./fetch-evidence.mjs";
|
|
9
|
+
import {
|
|
10
|
+
isUnqualifiedPropertySelector,
|
|
11
|
+
hasValidPropertySelectorPredicate,
|
|
12
|
+
evaluateSupplyPath,
|
|
13
|
+
evaluationLimitVerdict,
|
|
14
|
+
supplyPathAdsTxtPolicy,
|
|
15
|
+
parseInventoryPartnerDomains
|
|
16
|
+
} from "./evaluate.mjs";
|
|
17
|
+
import { assertRegistrySupplyPathResult, validateSupplyPathRequest } from "./validation.mjs";
|
|
18
|
+
const MAX_PROPERTY_SCOPE_VALUES = 32768;
|
|
19
|
+
const MAX_PROPERTY_SELECTOR_EXPANSIONS = 4096;
|
|
20
|
+
async function verifySupplyPath(request, options = { source: "authoritative" }) {
|
|
21
|
+
const normalized = validateSupplyPathRequest(request);
|
|
22
|
+
if (options.source === "registry") {
|
|
23
|
+
const result = await (options.registry ?? new RegistryClient()).verifySupplyPath(normalized);
|
|
24
|
+
assertRegistrySupplyPathResult(result, normalized);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
if (options.source !== "authoritative") throw new TypeError("source must be registry or authoritative");
|
|
28
|
+
const session = new SupplyPathEvidenceSession(options);
|
|
29
|
+
try {
|
|
30
|
+
return await withAbortSignal(
|
|
31
|
+
[session.signal],
|
|
32
|
+
void 0,
|
|
33
|
+
() => verifyAuthoritativeSupplyPath(normalized, options, session)
|
|
34
|
+
);
|
|
35
|
+
} finally {
|
|
36
|
+
session.close();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function verifyAuthoritativeSupplyPath(normalized, options, session) {
|
|
40
|
+
if (options.propertySelectors !== void 0) {
|
|
41
|
+
if (!Array.isArray(options.propertySelectors) || !options.propertySelectors.length)
|
|
42
|
+
throw new TypeError("propertySelectors must be non-empty");
|
|
43
|
+
if (options.propertySelectors.length > 1024)
|
|
44
|
+
throw new TypeError("propertySelectors exceed the evaluation work limit");
|
|
45
|
+
for (const raw of options.propertySelectors) {
|
|
46
|
+
if (!isUnqualifiedPropertySelector(raw)) throw new TypeError("Unsupported product property selector fields");
|
|
47
|
+
if (!hasValidPropertySelectorPredicate(raw))
|
|
48
|
+
throw new TypeError("Product property selector predicate conflicts with selection_type");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const [ownerManifest, hostManifest] = await Promise.all([
|
|
52
|
+
session.adagents(normalized.owner_domain),
|
|
53
|
+
session.adagents(normalized.host_domain)
|
|
54
|
+
]);
|
|
55
|
+
const input = {
|
|
56
|
+
ownerDomain: normalized.owner_domain,
|
|
57
|
+
hostDomain: normalized.host_domain,
|
|
58
|
+
agentUrl: normalized.agent_url,
|
|
59
|
+
collectionId: normalized.collection_id,
|
|
60
|
+
ownerManifest,
|
|
61
|
+
hostManifest,
|
|
62
|
+
requireExplicitOwnerPublisherDomain: session.crossOriginAuthorities.has(normalized.owner_domain),
|
|
63
|
+
requireExplicitHostPublisherDomain: session.crossOriginAuthorities.has(normalized.host_domain),
|
|
64
|
+
hostInventoryPartnerDomains: null,
|
|
65
|
+
hostInventoryPartnerDomainsByFile: void 0,
|
|
66
|
+
heldRevocations: {
|
|
67
|
+
owner: session.revocations.get(normalized.owner_domain)?.map((r) => r.publisher_domain),
|
|
68
|
+
host: session.revocations.get(normalized.host_domain)?.map((r) => r.publisher_domain)
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const scopedInput = input;
|
|
72
|
+
let selectorLimitExceeded = false;
|
|
73
|
+
if (options.propertySelectors !== void 0) {
|
|
74
|
+
const ids = /* @__PURE__ */ new Set();
|
|
75
|
+
const rawProperties = hostManifest?.properties;
|
|
76
|
+
if (Array.isArray(rawProperties) && rawProperties.length > 1024) selectorLimitExceeded = true;
|
|
77
|
+
const properties = selectorLimitExceeded ? [] : records(rawProperties).filter(
|
|
78
|
+
(p) => p.publisher_domain === void 0 && !input.requireExplicitHostPublisherDomain || domain(p.publisher_domain) === normalized.host_domain
|
|
79
|
+
);
|
|
80
|
+
let unresolved = false;
|
|
81
|
+
let scopeValues = properties.length;
|
|
82
|
+
const availableTags = /* @__PURE__ */ new Set();
|
|
83
|
+
for (const property of properties) {
|
|
84
|
+
session.assertActive();
|
|
85
|
+
if (property.tags === void 0) continue;
|
|
86
|
+
if (!Array.isArray(property.tags) || property.tags.length > 1024 || property.tags.some((tag) => typeof tag !== "string" || !tag.trim())) {
|
|
87
|
+
selectorLimitExceeded = true;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
scopeValues += property.tags.length;
|
|
91
|
+
if (scopeValues > MAX_PROPERTY_SCOPE_VALUES) {
|
|
92
|
+
selectorLimitExceeded = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
for (const tag of property.tags) {
|
|
96
|
+
if (tag.length > 8192) selectorLimitExceeded = true;
|
|
97
|
+
availableTags.add(tag);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const selectors = [];
|
|
101
|
+
for (const raw of options.propertySelectors) {
|
|
102
|
+
session.assertActive();
|
|
103
|
+
if (selectorLimitExceeded) continue;
|
|
104
|
+
const selector = parsePublisherPropertySelector(raw);
|
|
105
|
+
const expansionCount = "publisher_domains" in selector ? selector.publisher_domains.length : 1;
|
|
106
|
+
if (selectors.length + expansionCount > MAX_PROPERTY_SELECTOR_EXPANSIONS) {
|
|
107
|
+
selectorLimitExceeded = true;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const expanded = expandPublisherPropertySelector(selector);
|
|
111
|
+
selectors.push(...expanded);
|
|
112
|
+
}
|
|
113
|
+
const selectedTags = /* @__PURE__ */ new Set();
|
|
114
|
+
let selectAll = false;
|
|
115
|
+
for (const single of selectors) {
|
|
116
|
+
session.assertActive();
|
|
117
|
+
scopeValues += 1;
|
|
118
|
+
if (scopeValues > MAX_PROPERTY_SCOPE_VALUES) selectorLimitExceeded = true;
|
|
119
|
+
if (!selectorLimitExceeded) {
|
|
120
|
+
if (domain(single.publisher_domain) !== normalized.host_domain)
|
|
121
|
+
throw new TypeError("propertySelectors must name host_domain");
|
|
122
|
+
if (single.selection_type === "by_id") {
|
|
123
|
+
if (!strings(single.property_ids)) throw new TypeError("Invalid product property IDs");
|
|
124
|
+
scopeValues += single.property_ids.length;
|
|
125
|
+
if (single.property_ids.length > 1024 || single.property_ids.some((id) => id.length > 8192))
|
|
126
|
+
selectorLimitExceeded = true;
|
|
127
|
+
for (const id of single.property_ids) ids.add(id);
|
|
128
|
+
} else {
|
|
129
|
+
if (single.selection_type === "by_tag" && !strings(single.property_tags))
|
|
130
|
+
throw new TypeError("Invalid product property tags");
|
|
131
|
+
if (single.selection_type === "all") {
|
|
132
|
+
selectAll = true;
|
|
133
|
+
if (!properties.length) unresolved = true;
|
|
134
|
+
} else {
|
|
135
|
+
scopeValues += single.property_tags.length;
|
|
136
|
+
if (single.property_tags.length > 1024 || single.property_tags.some((tag) => tag.length > 8192))
|
|
137
|
+
selectorLimitExceeded = true;
|
|
138
|
+
let matched = false;
|
|
139
|
+
for (const tag of single.property_tags) {
|
|
140
|
+
if (!availableTags.has(tag)) continue;
|
|
141
|
+
matched = true;
|
|
142
|
+
selectedTags.add(tag);
|
|
143
|
+
}
|
|
144
|
+
if (!matched) unresolved = true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (scopeValues > MAX_PROPERTY_SCOPE_VALUES) selectorLimitExceeded = true;
|
|
150
|
+
if (!selectorLimitExceeded && (selectAll || selectedTags.size)) {
|
|
151
|
+
for (const property of properties) {
|
|
152
|
+
session.assertActive();
|
|
153
|
+
const selected = selectAll || Array.isArray(property.tags) && property.tags.some((tag) => selectedTags.has(tag));
|
|
154
|
+
if (!selected) continue;
|
|
155
|
+
if (typeof property.property_id !== "string" || !property.property_id.length) unresolved = true;
|
|
156
|
+
else ids.add(property.property_id);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
scopedInput.requiredHostPropertyIds = selectorLimitExceeded || unresolved ? [] : [...ids];
|
|
160
|
+
}
|
|
161
|
+
session.assertActive();
|
|
162
|
+
let verdict = selectorLimitExceeded ? evaluationLimitVerdict() : evaluateSupplyPath(scopedInput);
|
|
163
|
+
session.assertActive();
|
|
164
|
+
if (!verdict.legs.host_authorization.ok && verdict.legs.host_authorization.failure !== "evaluation_limit_exceeded") {
|
|
165
|
+
const policy = supplyPathAdsTxtPolicy(scopedInput);
|
|
166
|
+
const responses = await Promise.all(
|
|
167
|
+
policy.files.map((kind) => session.read(normalized.host_domain, kind, `https://${normalized.host_domain}/${kind}`))
|
|
168
|
+
);
|
|
169
|
+
input.hostInventoryPartnerDomainsByFile = Object.fromEntries(
|
|
170
|
+
policy.files.map((file, index) => [
|
|
171
|
+
file,
|
|
172
|
+
responses[index] ? parseInventoryPartnerDomains(new TextDecoder().decode(responses[index].body)) : null
|
|
173
|
+
])
|
|
174
|
+
);
|
|
175
|
+
session.assertActive();
|
|
176
|
+
verdict = evaluateSupplyPath(scopedInput);
|
|
177
|
+
session.assertActive();
|
|
178
|
+
} else if (verdict.legs.host_authorization.ok) {
|
|
179
|
+
verdict = evaluateSupplyPath({ ...scopedInput, inventoryPartnerDomainEvaluated: false });
|
|
180
|
+
session.assertActive();
|
|
181
|
+
}
|
|
182
|
+
session.assertActive();
|
|
183
|
+
return {
|
|
184
|
+
...verdict,
|
|
185
|
+
...normalized,
|
|
186
|
+
source: "authoritative",
|
|
187
|
+
...options.propertySelectors ? { property_selectors: options.propertySelectors } : {},
|
|
188
|
+
sources: {
|
|
189
|
+
owner_adagents_url: `https://${normalized.owner_domain}/.well-known/adagents.json`,
|
|
190
|
+
host_adagents_url: `https://${normalized.host_domain}/.well-known/adagents.json`,
|
|
191
|
+
cached: false,
|
|
192
|
+
held_revocations: [...session.revocations].filter(([authority]) => authority === normalized.owner_domain || authority === normalized.host_domain).map(([authority, entries]) => ({ authority, entries })),
|
|
193
|
+
evidence: session.evidence.filter(
|
|
194
|
+
(e) => e.publisher_domain === normalized.owner_domain || e.publisher_domain === normalized.host_domain
|
|
195
|
+
)
|
|
196
|
+
},
|
|
197
|
+
checked_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
export {
|
|
201
|
+
verifyAuthoritativeSupplyPath,
|
|
202
|
+
verifySupplyPath
|
|
203
|
+
};
|
|
@@ -148,7 +148,7 @@ function preflightSkip(vector, kind, contract, options) {
|
|
|
148
148
|
return { ...base, skipped: true, skip_reason: "operator_skip" };
|
|
149
149
|
}
|
|
150
150
|
if (options.agentCapability) {
|
|
151
|
-
const mismatch = capabilityMismatch(vector, options.agentCapability);
|
|
151
|
+
const mismatch = capabilityMismatch(vector, kind, options.agentCapability);
|
|
152
152
|
if (mismatch) {
|
|
153
153
|
return { ...base, skipped: true, skip_reason: "capability_profile_mismatch", diagnostic: mismatch };
|
|
154
154
|
}
|
|
@@ -474,23 +474,33 @@ function contentDigestStructuralMismatch(vector, agentCoversContentDigest) {
|
|
|
474
474
|
}
|
|
475
475
|
return void 0;
|
|
476
476
|
}
|
|
477
|
+
const CONTENT_DIGEST_POLICY_REFUSALS = /* @__PURE__ */ new Set([
|
|
478
|
+
"request_signature_components_incomplete",
|
|
479
|
+
"request_signature_components_unexpected"
|
|
480
|
+
]);
|
|
481
|
+
function contentDigestDeclarationMismatch(vector, kind, agentPolicy) {
|
|
482
|
+
const vectorCd = vector.verifier_capability.covers_content_digest;
|
|
483
|
+
if (vectorCd === "either" || vectorCd === agentPolicy) return void 0;
|
|
484
|
+
if (agentPolicy === "either") {
|
|
485
|
+
const expected = kind === "negative" ? vector.expected_error_code : void 0;
|
|
486
|
+
if (expected === void 0 || !CONTENT_DIGEST_POLICY_REFUSALS.has(expected)) return void 0;
|
|
487
|
+
}
|
|
488
|
+
return `Vector asserts covers_content_digest='${vectorCd}' but agent declares '${agentPolicy}'. The vector can't grade against this profile \u2014 its expected verifier behavior doesn't match what the agent implements.`;
|
|
489
|
+
}
|
|
477
490
|
function contentDigestPolicyMismatch(vector, kind, agentPolicy) {
|
|
478
491
|
if (kind === "negative") {
|
|
479
|
-
const
|
|
480
|
-
if (
|
|
481
|
-
return `Vector asserts covers_content_digest='${vectorCd}' but agent declares '${agentPolicy}'. The agent's policy is incompatible with the vector's expected verifier behavior.`;
|
|
482
|
-
}
|
|
492
|
+
const declared = contentDigestDeclarationMismatch(vector, kind, agentPolicy);
|
|
493
|
+
if (declared) return declared;
|
|
483
494
|
}
|
|
484
495
|
return contentDigestStructuralMismatch(vector, agentPolicy);
|
|
485
496
|
}
|
|
486
|
-
function capabilityMismatch(vector, agentCap) {
|
|
497
|
+
function capabilityMismatch(vector, kind, agentCap) {
|
|
487
498
|
const vectorCap = vector.verifier_capability;
|
|
488
499
|
if (vectorCap.supported !== agentCap.supported) {
|
|
489
500
|
return `Vector asserts supported=${vectorCap.supported} but agent declares supported=${agentCap.supported}. Verify the agent's request_signing capability block.`;
|
|
490
501
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
502
|
+
const declared = contentDigestDeclarationMismatch(vector, kind, agentCap.covers_content_digest);
|
|
503
|
+
if (declared) return declared;
|
|
494
504
|
const structural = contentDigestStructuralMismatch(vector, agentCap.covers_content_digest);
|
|
495
505
|
if (structural) return structural;
|
|
496
506
|
const vectorRequiredFor = vectorCap.required_for ?? [];
|
|
@@ -128,7 +128,7 @@ function preflightSkip(vector, kind, contract, options) {
|
|
|
128
128
|
return { ...base, skipped: true, skip_reason: "operator_skip" };
|
|
129
129
|
}
|
|
130
130
|
if (options.agentCapability) {
|
|
131
|
-
const mismatch = capabilityMismatch(vector, options.agentCapability);
|
|
131
|
+
const mismatch = capabilityMismatch(vector, kind, options.agentCapability);
|
|
132
132
|
if (mismatch) {
|
|
133
133
|
return { ...base, skipped: true, skip_reason: "capability_profile_mismatch", diagnostic: mismatch };
|
|
134
134
|
}
|
|
@@ -454,23 +454,33 @@ function contentDigestStructuralMismatch(vector, agentCoversContentDigest) {
|
|
|
454
454
|
}
|
|
455
455
|
return void 0;
|
|
456
456
|
}
|
|
457
|
+
const CONTENT_DIGEST_POLICY_REFUSALS = /* @__PURE__ */ new Set([
|
|
458
|
+
"request_signature_components_incomplete",
|
|
459
|
+
"request_signature_components_unexpected"
|
|
460
|
+
]);
|
|
461
|
+
function contentDigestDeclarationMismatch(vector, kind, agentPolicy) {
|
|
462
|
+
const vectorCd = vector.verifier_capability.covers_content_digest;
|
|
463
|
+
if (vectorCd === "either" || vectorCd === agentPolicy) return void 0;
|
|
464
|
+
if (agentPolicy === "either") {
|
|
465
|
+
const expected = kind === "negative" ? vector.expected_error_code : void 0;
|
|
466
|
+
if (expected === void 0 || !CONTENT_DIGEST_POLICY_REFUSALS.has(expected)) return void 0;
|
|
467
|
+
}
|
|
468
|
+
return `Vector asserts covers_content_digest='${vectorCd}' but agent declares '${agentPolicy}'. The vector can't grade against this profile \u2014 its expected verifier behavior doesn't match what the agent implements.`;
|
|
469
|
+
}
|
|
457
470
|
function contentDigestPolicyMismatch(vector, kind, agentPolicy) {
|
|
458
471
|
if (kind === "negative") {
|
|
459
|
-
const
|
|
460
|
-
if (
|
|
461
|
-
return `Vector asserts covers_content_digest='${vectorCd}' but agent declares '${agentPolicy}'. The agent's policy is incompatible with the vector's expected verifier behavior.`;
|
|
462
|
-
}
|
|
472
|
+
const declared = contentDigestDeclarationMismatch(vector, kind, agentPolicy);
|
|
473
|
+
if (declared) return declared;
|
|
463
474
|
}
|
|
464
475
|
return contentDigestStructuralMismatch(vector, agentPolicy);
|
|
465
476
|
}
|
|
466
|
-
function capabilityMismatch(vector, agentCap) {
|
|
477
|
+
function capabilityMismatch(vector, kind, agentCap) {
|
|
467
478
|
const vectorCap = vector.verifier_capability;
|
|
468
479
|
if (vectorCap.supported !== agentCap.supported) {
|
|
469
480
|
return `Vector asserts supported=${vectorCap.supported} but agent declares supported=${agentCap.supported}. Verify the agent's request_signing capability block.`;
|
|
470
481
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
482
|
+
const declared = contentDigestDeclarationMismatch(vector, kind, agentCap.covers_content_digest);
|
|
483
|
+
if (declared) return declared;
|
|
474
484
|
const structural = contentDigestStructuralMismatch(vector, agentCap.covers_content_digest);
|
|
475
485
|
if (structural) return structural;
|
|
476
486
|
const vectorRequiredFor = vectorCap.required_for ?? [];
|
|
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
|
|
|
197
197
|
*/
|
|
198
198
|
export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
|
|
199
199
|
/** Capability semver emitted in run summaries and used for advisory expiry. */
|
|
200
|
-
export declare const RUNNER_CAPABILITY_VERSION = "14.0.0-rc.
|
|
200
|
+
export declare const RUNNER_CAPABILITY_VERSION = "14.0.0-rc.40";
|
|
201
201
|
/** True when a failed validation contributes to the owning step's grade. */
|
|
202
202
|
export declare function validationFailsStep(result: ValidationResult): boolean;
|
|
203
203
|
/**
|
|
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
|
|
|
197
197
|
*/
|
|
198
198
|
export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
|
|
199
199
|
/** Capability semver emitted in run summaries and used for advisory expiry. */
|
|
200
|
-
export declare const RUNNER_CAPABILITY_VERSION = "14.0.0-rc.
|
|
200
|
+
export declare const RUNNER_CAPABILITY_VERSION = "14.0.0-rc.40";
|
|
201
201
|
/** True when a failed validation contributes to the owning step's grade. */
|
|
202
202
|
export declare function validationFailsStep(result: ValidationResult): boolean;
|
|
203
203
|
/**
|
|
@@ -52,7 +52,7 @@ export type CanonicalPlacement = Omit<CanonicalCreativeResponse<Placement>, 'for
|
|
|
52
52
|
format_ids?: never;
|
|
53
53
|
format_options?: NonEmptyCanonicalFormatDeclarations;
|
|
54
54
|
};
|
|
55
|
-
export type CanonicalProduct = Omit<CanonicalCreativeResponse<Product>, 'format_ids' | 'format_options' | 'placements'> & {
|
|
55
|
+
export type CanonicalProduct = Omit<CanonicalCreativeResponse<Product>, 'format_ids' | 'format_options' | 'placements'> & import('../../supply-path/products.mjs').ProductSupplyPathAnnotation & {
|
|
56
56
|
product_id: string;
|
|
57
57
|
format_ids?: never;
|
|
58
58
|
format_options: NonEmptyCanonicalFormatDeclarations;
|
|
@@ -52,7 +52,7 @@ export type CanonicalPlacement = Omit<CanonicalCreativeResponse<Placement>, 'for
|
|
|
52
52
|
format_ids?: never;
|
|
53
53
|
format_options?: NonEmptyCanonicalFormatDeclarations;
|
|
54
54
|
};
|
|
55
|
-
export type CanonicalProduct = Omit<CanonicalCreativeResponse<Product>, 'format_ids' | 'format_options' | 'placements'> & {
|
|
55
|
+
export type CanonicalProduct = Omit<CanonicalCreativeResponse<Product>, 'format_ids' | 'format_options' | 'placements'> & import('../../supply-path/products').ProductSupplyPathAnnotation & {
|
|
56
56
|
product_id: string;
|
|
57
57
|
format_ids?: never;
|
|
58
58
|
format_options: NonEmptyCanonicalFormatDeclarations;
|
|
@@ -449,11 +449,13 @@ function ensureInit(version) {
|
|
|
449
449
|
}
|
|
450
450
|
function ensureCoreLoaded(s) {
|
|
451
451
|
if (s.coreLoaded) return;
|
|
452
|
-
const responseToolFiles = /* @__PURE__ */ new
|
|
452
|
+
const responseToolFiles = /* @__PURE__ */ new Set();
|
|
453
|
+
const responseToolIds = /* @__PURE__ */ new Set();
|
|
453
454
|
for (const [key, file] of s.fileIndex) {
|
|
454
455
|
if (key.endsWith("::request")) continue;
|
|
455
|
-
|
|
456
|
-
|
|
456
|
+
responseToolFiles.add(file);
|
|
457
|
+
const schema = loadJson(file);
|
|
458
|
+
if (typeof schema.$id === "string") responseToolIds.add(schema.$id);
|
|
457
459
|
}
|
|
458
460
|
const registeredIds = getAjvRegisteredIds(s.ajv);
|
|
459
461
|
for (const entry of (0, import_fs.readdirSync)(s.root, { withFileTypes: true })) {
|
|
@@ -461,9 +463,9 @@ function ensureCoreLoaded(s) {
|
|
|
461
463
|
if (!isRuntimeSchemaDirectory(entry.name)) continue;
|
|
462
464
|
const abs = import_path.default.join(s.root, entry.name);
|
|
463
465
|
for (const file of walkJsonFiles(abs)) {
|
|
464
|
-
const responseDirection = responseToolFiles.get(file);
|
|
465
466
|
const schema = loadJson(file);
|
|
466
|
-
const
|
|
467
|
+
const isResponseTool = responseToolFiles.has(file) || typeof schema.$id === "string" && responseToolIds.has(schema.$id);
|
|
468
|
+
const schemaToRegister = isResponseTool ? relaxResponseRoot(schema) : schema;
|
|
467
469
|
if (typeof schemaToRegister.$id === "string" && !registeredIds.has(schemaToRegister.$id)) {
|
|
468
470
|
s.ajv.addSchema(schemaToRegister);
|
|
469
471
|
registeredIds.add(schemaToRegister.$id);
|
|
@@ -399,11 +399,13 @@ function ensureInit(version) {
|
|
|
399
399
|
}
|
|
400
400
|
function ensureCoreLoaded(s) {
|
|
401
401
|
if (s.coreLoaded) return;
|
|
402
|
-
const responseToolFiles = /* @__PURE__ */ new
|
|
402
|
+
const responseToolFiles = /* @__PURE__ */ new Set();
|
|
403
|
+
const responseToolIds = /* @__PURE__ */ new Set();
|
|
403
404
|
for (const [key, file] of s.fileIndex) {
|
|
404
405
|
if (key.endsWith("::request")) continue;
|
|
405
|
-
|
|
406
|
-
|
|
406
|
+
responseToolFiles.add(file);
|
|
407
|
+
const schema = loadJson(file);
|
|
408
|
+
if (typeof schema.$id === "string") responseToolIds.add(schema.$id);
|
|
407
409
|
}
|
|
408
410
|
const registeredIds = getAjvRegisteredIds(s.ajv);
|
|
409
411
|
for (const entry of readdirSync(s.root, { withFileTypes: true })) {
|
|
@@ -411,9 +413,9 @@ function ensureCoreLoaded(s) {
|
|
|
411
413
|
if (!isRuntimeSchemaDirectory(entry.name)) continue;
|
|
412
414
|
const abs = path.join(s.root, entry.name);
|
|
413
415
|
for (const file of walkJsonFiles(abs)) {
|
|
414
|
-
const responseDirection = responseToolFiles.get(file);
|
|
415
416
|
const schema = loadJson(file);
|
|
416
|
-
const
|
|
417
|
+
const isResponseTool = responseToolFiles.has(file) || typeof schema.$id === "string" && responseToolIds.has(schema.$id);
|
|
418
|
+
const schemaToRegister = isResponseTool ? relaxResponseRoot(schema) : schema;
|
|
417
419
|
if (typeof schemaToRegister.$id === "string" && !registeredIds.has(schemaToRegister.$id)) {
|
|
418
420
|
s.ajv.addSchema(schemaToRegister);
|
|
419
421
|
registeredIds.add(schemaToRegister.$id);
|
package/dist/lib/version.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AdCP SDK library version
|
|
3
3
|
*/
|
|
4
|
-
export declare const LIBRARY_VERSION = "14.0.0-rc.
|
|
4
|
+
export declare const LIBRARY_VERSION = "14.0.0-rc.40";
|
|
5
5
|
/**
|
|
6
6
|
* AdCP specification version this library is built for
|
|
7
7
|
*/
|
|
@@ -33,10 +33,10 @@ export type AdcpVersion = (typeof COMPATIBLE_ADCP_VERSIONS)[number];
|
|
|
33
33
|
* Full version information
|
|
34
34
|
*/
|
|
35
35
|
export declare const VERSION_INFO: {
|
|
36
|
-
readonly library: "14.0.0-rc.
|
|
36
|
+
readonly library: "14.0.0-rc.40";
|
|
37
37
|
readonly adcp: "3.2.0-rc.3";
|
|
38
38
|
readonly compatibleVersions: readonly ["v2.5", "v2.6", "v3", "3.0.0", "3.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4", "3.0.5", "3.0.6", "3.0.7", "3.0.8", "3.0.9", "3.0.10", "3.0.11", "3.0.12", "3.0.13", "3.0.14", "3.0.15", "3.0.16", "3.0.17", "3.0.18", "3.0.19", "3.0.20", "3.0.21", "3.0.22", "3.0.23", "3.0.24", "3.0.25", "3.1.0", "3.1", "3.1.1", "3.1.2", "3.1.3", "3.1.4", "3.1.5", "3.1.6", "3.1.7", "3.1.8", "3.1.9", "3.1.10", "3.1.11", "3.1.12", "3.1.13", "3.1.14", "3.1.15", "3.1.16", "3.1.17", "3.1.18", "3.2.0-rc.3", "3.2-rc.3"];
|
|
39
|
-
readonly generatedAt: "2026-09-
|
|
39
|
+
readonly generatedAt: "2026-09-19T13:27:46.372Z";
|
|
40
40
|
};
|
|
41
41
|
/**
|
|
42
42
|
* Get the AdCP specification version this library is built for
|
package/dist/lib/version.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AdCP SDK library version
|
|
3
3
|
*/
|
|
4
|
-
export declare const LIBRARY_VERSION = "14.0.0-rc.
|
|
4
|
+
export declare const LIBRARY_VERSION = "14.0.0-rc.40";
|
|
5
5
|
/**
|
|
6
6
|
* AdCP specification version this library is built for
|
|
7
7
|
*/
|
|
@@ -33,10 +33,10 @@ export type AdcpVersion = (typeof COMPATIBLE_ADCP_VERSIONS)[number];
|
|
|
33
33
|
* Full version information
|
|
34
34
|
*/
|
|
35
35
|
export declare const VERSION_INFO: {
|
|
36
|
-
readonly library: "14.0.0-rc.
|
|
36
|
+
readonly library: "14.0.0-rc.40";
|
|
37
37
|
readonly adcp: "3.2.0-rc.3";
|
|
38
38
|
readonly compatibleVersions: readonly ["v2.5", "v2.6", "v3", "3.0.0", "3.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4", "3.0.5", "3.0.6", "3.0.7", "3.0.8", "3.0.9", "3.0.10", "3.0.11", "3.0.12", "3.0.13", "3.0.14", "3.0.15", "3.0.16", "3.0.17", "3.0.18", "3.0.19", "3.0.20", "3.0.21", "3.0.22", "3.0.23", "3.0.24", "3.0.25", "3.1.0", "3.1", "3.1.1", "3.1.2", "3.1.3", "3.1.4", "3.1.5", "3.1.6", "3.1.7", "3.1.8", "3.1.9", "3.1.10", "3.1.11", "3.1.12", "3.1.13", "3.1.14", "3.1.15", "3.1.16", "3.1.17", "3.1.18", "3.2.0-rc.3", "3.2-rc.3"];
|
|
39
|
-
readonly generatedAt: "2026-09-
|
|
39
|
+
readonly generatedAt: "2026-09-19T13:27:46.372Z";
|
|
40
40
|
};
|
|
41
41
|
/**
|
|
42
42
|
* Get the AdCP specification version this library is built for
|
package/dist/lib/version.js
CHANGED
|
@@ -31,7 +31,7 @@ __export(version_exports, {
|
|
|
31
31
|
toReleasePrecisionVersion: () => toReleasePrecisionVersion
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(version_exports);
|
|
34
|
-
const LIBRARY_VERSION = "14.0.0-rc.
|
|
34
|
+
const LIBRARY_VERSION = "14.0.0-rc.40";
|
|
35
35
|
const ADCP_VERSION = "3.2.0-rc.3";
|
|
36
36
|
const ADCP_MAJOR_VERSION = 3;
|
|
37
37
|
const COMPATIBLE_ADCP_VERSIONS = [
|
|
@@ -89,10 +89,10 @@ const COMPATIBLE_ADCP_VERSIONS = [
|
|
|
89
89
|
"3.2-rc.3"
|
|
90
90
|
];
|
|
91
91
|
const VERSION_INFO = {
|
|
92
|
-
library: "14.0.0-rc.
|
|
92
|
+
library: "14.0.0-rc.40",
|
|
93
93
|
adcp: "3.2.0-rc.3",
|
|
94
94
|
compatibleVersions: COMPATIBLE_ADCP_VERSIONS,
|
|
95
|
-
generatedAt: "2026-09-
|
|
95
|
+
generatedAt: "2026-09-19T13:27:46.372Z"
|
|
96
96
|
};
|
|
97
97
|
function getAdcpVersion() {
|
|
98
98
|
return ADCP_VERSION;
|
package/dist/lib/version.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const LIBRARY_VERSION = "14.0.0-rc.
|
|
1
|
+
const LIBRARY_VERSION = "14.0.0-rc.40";
|
|
2
2
|
const ADCP_VERSION = "3.2.0-rc.3";
|
|
3
3
|
const ADCP_MAJOR_VERSION = 3;
|
|
4
4
|
const COMPATIBLE_ADCP_VERSIONS = [
|
|
@@ -56,10 +56,10 @@ const COMPATIBLE_ADCP_VERSIONS = [
|
|
|
56
56
|
"3.2-rc.3"
|
|
57
57
|
];
|
|
58
58
|
const VERSION_INFO = {
|
|
59
|
-
library: "14.0.0-rc.
|
|
59
|
+
library: "14.0.0-rc.40",
|
|
60
60
|
adcp: "3.2.0-rc.3",
|
|
61
61
|
compatibleVersions: COMPATIBLE_ADCP_VERSIONS,
|
|
62
|
-
generatedAt: "2026-09-
|
|
62
|
+
generatedAt: "2026-09-19T13:27:46.372Z"
|
|
63
63
|
};
|
|
64
64
|
function getAdcpVersion() {
|
|
65
65
|
return ADCP_VERSION;
|
package/docs/TYPE-SUMMARY.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AdCP Type Summary
|
|
2
2
|
|
|
3
|
-
> Generated at: 2026-09-
|
|
4
|
-
> @adcp/sdk v14.0.0-rc.
|
|
3
|
+
> Generated at: 2026-09-19
|
|
4
|
+
> @adcp/sdk v14.0.0-rc.40
|
|
5
5
|
|
|
6
6
|
Curated reference of the types that matter for using the AdCP client. For full generated types see `src/lib/types/tools.generated.ts` and `src/lib/types/core.generated.ts`.
|
|
7
7
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Supply-path verification
|
|
2
|
+
|
|
3
|
+
Use `verifySupplyPath` to check an owner-sold collection on a host publisher. It returns a state and five independently diagnosed legs: owner collection declaration, owner distribution/carriage, owner agent declaration, host authorization, and `INVENTORYPARTNERDOMAIN` evidence.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { verifySupplyPath } from '@adcp/sdk';
|
|
7
|
+
|
|
8
|
+
const result = await verifySupplyPath({
|
|
9
|
+
owner_domain: 'channel-owner.example',
|
|
10
|
+
host_domain: 'hoststream.example',
|
|
11
|
+
agent_url: 'https://sales.channel-owner.example',
|
|
12
|
+
collection_id: 'retro_news',
|
|
13
|
+
}, { source: 'authoritative', timeoutMs: 15_000, retainEvidenceBodies: true });
|
|
14
|
+
|
|
15
|
+
if (result.state !== 'verified_owner_sold') {
|
|
16
|
+
// Apply your buying policy. Never treat owner_attested as authorization.
|
|
17
|
+
console.log(result.legs);
|
|
18
|
+
}
|
|
19
|
+
// Store this evidence with the decision, including exact bytes when enabled.
|
|
20
|
+
console.log(result.sources.evidence);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
| State | Meaning |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `verified_owner_sold` | The owner's collection and carriage references resolve, and a host grant covers the complete carried property scope for this agent and collection without unevaluated constraints. |
|
|
26
|
+
| `host_delegated` | The host's ads.txt/app-ads.txt names the owner, and the owner declares the agent and collection. This weaker evidence does not bind the host to this agent or collection. Accept only under an explicit buyer policy. |
|
|
27
|
+
| `owner_attested` | The owner asserts carriage. This is discovery information, never host sales authorization. |
|
|
28
|
+
| `unverified` | The evidence cannot establish a path. |
|
|
29
|
+
|
|
30
|
+
The state is a supply-path decision, not a purchase guarantee, exclusivity claim, proof of delivery, or blanket product authorization. Country, effective-time and placement restrictions require an applicable context; this API fails closed on owner or host entries carrying those restrictions, including unknown authorization fields. Independent unrestricted entries can still establish a path. Collection distribution identifiers identify channels in a catalog; they are not host property IDs and are never silently converted into property IDs.
|
|
31
|
+
|
|
32
|
+
`collection_id` is optional for a domain-level inquiry. The evaluator considers complete individual collection paths and returns the strongest one as `resolved_collection_id`; it never combines carriage from one collection with authorization for another. For a particular product, always check its explicit collection IDs.
|
|
33
|
+
|
|
34
|
+
## Registry mode
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { RegistryClient, verifySupplyPath } from '@adcp/sdk';
|
|
38
|
+
|
|
39
|
+
const request = {
|
|
40
|
+
owner_domain: 'channel-owner.example',
|
|
41
|
+
host_domain: 'hoststream.example',
|
|
42
|
+
agent_url: 'https://sales.channel-owner.example',
|
|
43
|
+
collection_id: 'retro_news',
|
|
44
|
+
};
|
|
45
|
+
const registry = new RegistryClient();
|
|
46
|
+
const cached = await registry.verifySupplyPath(request);
|
|
47
|
+
// Equivalent:
|
|
48
|
+
await verifySupplyPath(request, { source: 'registry', registry });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This is a typed wrapper over `POST /api/registry/verify/supply-path`. It preserves the registry's verdict, diagnostics, URLs, `cached` flag, timestamp and additional response fields. HTTP failures and malformed/inconsistent response shapes reject the promise. The wrapper requires `semantics_version: "1"`; an older registry must upgrade before it can serve this API. Registry responses describe cached evidence; `checked_at` is the decision time, not proof that the origin files were fetched then. The registry includes `owner_fetched_at`, `host_fetched_at` and resolved URLs so cache provenance can be assessed separately. The wrapper does not promote registry provenance into authoritative evidence.
|
|
52
|
+
|
|
53
|
+
## Discovery annotations
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { annotateProductsSupplyPaths } from '@adcp/sdk';
|
|
57
|
+
|
|
58
|
+
// products must be the actual products returned by your seller.
|
|
59
|
+
const annotated = await annotateProductsSupplyPaths(products, sellerAgentUrl, {
|
|
60
|
+
source: 'authoritative',
|
|
61
|
+
maxPaths: 64,
|
|
62
|
+
});
|
|
63
|
+
for (const product of annotated) {
|
|
64
|
+
console.log(product.supply_path_state, product.supply_path_verification);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Alternatively, configure a client with `validation.supplyPathVerification: { source: 'authoritative' }`. Completed `get_products` and `list_products` responses are annotated before completion handlers receive them, including resumed completion paths. Annotation is opt-in because it performs additional network requests.
|
|
69
|
+
|
|
70
|
+
Products combining `publisher_properties` with a collection selector in a different publisher namespace receive `supply_path_state`, the weakest state across their distinct external paths. Details live in `supply_path_verification.paths`. Authoritative mode uses `scope: 'product_properties'` and resolves every product selector against the host manifest; a verified path must cover all selected properties. Registry mode uses `scope: 'owner_host_collection'` because the endpoint accepts no product property context. Its annotation cannot certify the product's selected properties. Neither mode certifies placements, countries, effective dates, or other product restrictions. The weaker `host_delegated` state never establishes concrete product property coverage.
|
|
71
|
+
Products, ordering and unrelated seller fields are preserved. Computed annotation replaces any seller-authored verification fields. Local-only products have no external-path annotation. Invalid or omitted product `collection_ids` and path-limit overflow produce `unverified` with errors. Network policy refusal, unconfirmed authoritative-location changes, cancellation and registry failures reject the operation instead of returning a seller-authored or stale success. Batches deduplicate both paths and evidence fetches within the operation, run at most four paths concurrently, and default to 64 distinct paths (maximum 256).
|
|
72
|
+
|
|
73
|
+
## Selector types
|
|
74
|
+
|
|
75
|
+
`AuthorizationCollectionSelector.collection_ids` is optional: omission grants all collections declared by that exact publisher. Present `[]`, `null`, non-array values and mixed-type ID arrays are invalid, not bulk grants. `ProductCollectionSelector` requires a non-empty explicit ID tuple. `CollectionDistribution` accepts `property_ids`, `identifiers`, or both. Public discovery and registry authorization types preserve these distinctions.
|
|
76
|
+
|
|
77
|
+
## Network and evidence policy
|
|
78
|
+
|
|
79
|
+
Authoritative mode starts at each publisher's HTTPS `/.well-known/adagents.json`. It uses the SDK's existing SSRF-safe fetcher: every DNS address is checked, the connection is pinned to a validated address, TLS verification remains enabled, and private/link-local/metadata addresses are refused. It does not contact the agent endpoint. No authorization credentials are sent to publisher evidence URLs.
|
|
80
|
+
|
|
81
|
+
HTTP redirects are limited to three within the exact HTTPS origin. A publisher-origin `authoritative_location` pointer or `superseded_by` migration may explicitly delegate to another HTTPS origin once; that target cannot redirect again. Properties from a cross-origin document must explicitly name their `publisher_domain`; unscoped shared-network properties cannot authorize another publisher. Credentials, nonstandard ports, mixed pointer/catalog files and chained pointers fail closed. This stricter authoritative API does not use community catalogs, cached fallback documents or MANAGERDOMAIN discovery. Those discovery routes need additional publisher scope proofs before they can serve as enforcement evidence.
|
|
82
|
+
|
|
83
|
+
Collections in a cross-origin authoritative document must explicitly name their owning `publisher_domain`, just as shared host properties must identify their publisher. A domain pointing at an unrelated public catalog cannot borrow that catalog's collections, including through weaker inventory-partner evidence. Publisher-origin collection declarations may omit the field. Owner agent grants in shared cross-origin documents must also explicitly select that owner through `collections[].publisher_domain`; an absent `collections` constraint cannot authorize every member. Within that owner namespace, omitted `collection_ids` still grants all collections.
|
|
84
|
+
|
|
85
|
+
Authoritative locations are pinned only after the first successfully fetched, parsed, non-chained manifest with the required authorization envelope. Unavailable or malformed targets do not establish a pin. `SupplyPathAuthorityStore.check` is a non-mutating precheck that rejects a different existing pin before fetching its target. After validation, `observe` must atomically insert an absent pin or compare against the existing pin, rejecting a mismatch even when another request or operator changed it after the precheck. A successful `check` is neither a reservation nor authorization; verification requires a successful `observe`. Configure a durable shared store across restarts or workers, and perform the final comparison and insert in one transaction scoped to the trusted tenant and publisher. After independently confirming a publisher migration, update the durable pin or call `approveChange` on your in-memory instance. For the process default, import `defaultSupplyPathAuthorities` from `@adcp/sdk` and call `defaultSupplyPathAuthorities.approveChange(publisherDomain, confirmedHttpsLocation)`. Never approve changes from an automatic retry. First-successful-use pinning cannot detect an origin already compromised before that observation.
|
|
86
|
+
|
|
87
|
+
The default deadline is 15 seconds for the whole path (or whole annotation batch), configurable up to 60 seconds. Each decompressed response is capped at 256 KiB, configurable up to 20 MiB, with a 64 MiB aggregate evidence budget per operation. The evaluator rejects arrays above 1,024 entries, strings above 8,192 characters, total input above 32,768 values, or a conservative selector-work estimate above four million operations with `evaluation_limit_exceeded`. Cancellation is supported through `signal`. `trustedFetchFn` is an explicit advanced egress hook: it assumes responsibility for DNS resolution, address policy and DNS rebinding protection; its evidence reports `connection_pinned: false`. It must honor request cancellation and manual redirect handling.
|
|
88
|
+
|
|
89
|
+
Evidence contains requested/resolved URLs, HTTP status, fetch time, SHA-256 of exact decompressed bytes, byte count, connection-pinning status and structured failure codes. `retainEvidenceBodies` also retains those bytes as JSON-safe `body_base64`, including pointer and HTTP redirect documents. Retain the entire chain alongside an enforcement decision. Bodies are untrusted publisher content; store them as opaque evidence, not instructions or trusted log fields. A live fetch is an observation at `fetched_at`, not a permanent authorization grant; reverify before subsequent decisions when freshness matters.
|
|
90
|
+
|
|
91
|
+
`inventory_partner_domain.failure: 'not_evaluated'` means the IAB fetch was skipped because host authorization already established the strongest state. A 404 is fetched-and-absent (`not_declared`); transport failures remain `ads_txt_unavailable`, with the cause in evidence. Websites use ads.txt, apps use app-ads.txt, and mixed surfaces require the owner in every applicable file. `unsupported_constraints` identifies unevaluated grant fields in `detail`.
|
|
92
|
+
|
|
93
|
+
Validated publisher revocations are persisted immediately after parsing, before later fetches or authority-store operations can fail. They are held for seven days per publisher authority, including after the next manifest omits them. The default `InMemorySupplyPathRevocationStore` holds them for the process lifetime. For enforcement across restarts or multiple workers, configure a durable shared `SupplyPathRevocationStore`; its `observe` operation must be atomic and retain first-observation expiry by publisher domain within each authority, retaining the first-observation clock when `revoked_at` changes. Store failures and malformed revocation evidence reject verification. `sources.held_revocations` records the hold used in the decision. Keep observations from different publisher authorities isolated. The process-local store admits at most 1,024 live revocations per authority and 10,000 overall. Both process-local stores limit admission to 128 new authorities per 60-second window; the revocation store counts authorities with a new nonempty hold, and the pointer store counts first pins. Existing holds and pins remain readable during admission refusals. Pointer storage is capped at 10,000 authorities for the process lifetime. Overflow rejects verification without evicting live evidence or silently adopting a replacement pin.
|
|
94
|
+
|
|
95
|
+
These defaults are shared by callers in the process. A sustained many-domain attack can still exhaust their finite capacity, including domains controlled under a wildcard. Rate-limit verification requests by authenticated caller or tenant before invoking the API; publisher domain alone is not an abuse-control identity. For multi-tenant enforcement, pass tenant-scoped durable `revocationStore` and `authorityStore` instances. Capture the tenant from trusted application context in each instance, then key revocations by `(tenant, authority, publisher_domain)` and pins by `(tenant, publisher_domain)`. Make capacity/admission limits and transactions tenant-scoped, and reject exhausted writes instead of deleting live evidence. Neither store interface derives a tenant from counterparty evidence. Do not recover from capacity errors by silently replacing a store with an empty one.
|
|
96
|
+
|
|
97
|
+
Authority-store keys use the exact serialized HTTPS URL supplied by the verifier. URLs containing a fragment delimiter, including an empty trailing `#`, are refused. Preserve query and path spelling rather than merging potentially distinct server resources. The request deadline bounds the caller's wait; a durable `observe` transaction may complete after the caller times out. It still represents a successfully validated manifest, so retries must be idempotent and must not undo that pin or any retained denial.
|
|
98
|
+
|
|
99
|
+
## Contract and conformance
|
|
100
|
+
|
|
101
|
+
The state/leg contract originates in [adcp#6897](https://github.com/adcontextprotocol/adcp/pull/6897) and its fail-closed companion. Both implementations execute the same [canonical supply-path vectors](https://github.com/adcontextprotocol/adcp/tree/main/static/compliance/source/test-vectors/supply-path), including each verdict, resolved collection, leg failure, parser result, and applicable IAB-file policy. The SDK vendors the JSON byte-for-byte from a reviewed upstream commit; provenance and checksums are recorded in `test/fixtures/supply-path/source.json`. Updating it requires selecting a reviewed upstream commit and rerunning both implementations. The runtime `semantics_version` prevents silent acceptance of registries running the older fail-open evaluator.
|
package/docs/llms.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Ad Context Protocol (AdCP)
|
|
2
2
|
|
|
3
|
-
> Generated at: 2026-09-
|
|
4
|
-
> Library: @adcp/sdk v14.0.0-rc.
|
|
3
|
+
> Generated at: 2026-09-19
|
|
4
|
+
> Library: @adcp/sdk v14.0.0-rc.40
|
|
5
5
|
> AdCP major version: 3
|
|
6
6
|
> Canonical URL: https://adcontextprotocol.github.io/adcp-client/llms.txt
|
|
7
7
|
> Note: the `Library` stamp reflects the package.json version at doc-generation time. The narrative below describes the surface that lands on the next-published minor — including any 6.7 helpers documented here ahead of the release tag.
|