From 8d7ad903de650cef9216c7d0a2f621ea0d94ecae Mon Sep 17 00:00:00 2001 From: "Claude Code (triage)" Date: Wed, 20 May 2026 05:23:39 +0000 Subject: [PATCH 1/2] feat(adagents): permit parent-file inline resolution for publisher_properties selectors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #4825 — WG decision by @bokelley: endorse Option A. Adds parent-file inline resolution as an explicit MAY/SHOULD path alongside federated default for publisher_properties selectors. Federated resolution remains the trust root; inline is a per-domain optimization for managed-network scale (6,800+ inline properties in a single file). Trust invariant preserved: every matched inline property MUST carry an explicit publisher_domain matching the selector's addressed domains. Entries missing publisher_domain MUST NOT enter the inline result set. Conflict rule: when inline and federated disagree on (publisher_domain, property_id), consumer MUST use full federated object. by_id selectors remain federated-only (property IDs are publisher-scoped). https://claude.ai/code/session_013RpTM3r22Ge29GxsqKSYHK --- .changeset/4825-adagents-inline-resolution.md | 15 +++++++++++++ docs/governance/property/adagents.mdx | 22 ++++++++++++++----- .../core/publisher-property-selector.json | 4 ++-- 3 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 .changeset/4825-adagents-inline-resolution.md diff --git a/.changeset/4825-adagents-inline-resolution.md b/.changeset/4825-adagents-inline-resolution.md new file mode 100644 index 0000000000..6ebf3b2a0f --- /dev/null +++ b/.changeset/4825-adagents-inline-resolution.md @@ -0,0 +1,15 @@ +--- +"adcontextprotocol": minor +--- + +feat(adagents): permit parent-file inline resolution of `publisher_properties` selectors when matched top-level properties carry a `publisher_domain` matching the selector's addressed domains. + +Federated per-child resolution remains the default and the trust root. Inline is a per-domain optimization: consumers SHOULD prefer it when the parent file's `properties[]` contains entries whose `publisher_domain` matches the selector's `publisher_domains[]` and whose tags satisfy the predicate. The production reference for the managed-network pattern (cafemedia.com, ~6,800 represented domains) inlines properties — strict federation requires N HTTP fetches per authorization check, infeasible at managed-network scale. + +`by_id` selectors remain federated-only. Property IDs are publisher-scoped; fanning a fixed ID set across multiple publishers would silently authorize inventory at unintended publishers. + +Divergence rule: if inline and federated resolutions disagree on the same `(publisher_domain, property_id)`, federated wins. Revocation: inline path MUST honor parent-file `revoked_publisher_domains[]`; consumers SHOULD also cross-check the child's own when federated. + +Trust invariant: every matched inline property MUST carry an explicit `publisher_domain`. Properties missing this field MUST NOT be included in the inline resolution result set. + +Companion: adcp-client-python needs a matching SDK update to fan `publisher_domains[]` into per-domain resolution and consult parent-file inline first when matching properties carry `publisher_domain`. Tracked in adcontextprotocol/adcp-client-python#746. diff --git a/docs/governance/property/adagents.mdx b/docs/governance/property/adagents.mdx index cd9881d0d4..a7cb6abab3 100644 --- a/docs/governance/property/adagents.mdx +++ b/docs/governance/property/adagents.mdx @@ -243,8 +243,9 @@ When `https://{publisher}/.well-known/adagents.json` returns `404`, validators M 1. **Per-agent paths.** The agent entry directly carries the publisher domain under `publisher_properties[].publisher_domain`, inside `publisher_properties[].publisher_domains[]` (the compact managed-network form), or under `collections[].publisher_domain`. 2. **Property-level paths.** The agent entry references one or more top-level `properties[]` entries by ID (`property_ids`) or by tag (`property_tags`), and at least one resolved property carries a `publisher_domain` matching the source. This is the shape used in production by Mediavine and other managed networks where a property declares its `publisher_domain` once and many agents reference it indirectly. + 3. **Publisher-properties inline path.** When the agent entry uses `authorization_type: "publisher_properties"` and the consumer resolves the selector via the parent-file inline path (see [Fan-out resolution semantics](#fan-out-resolution-semantics)), each matched inline property's `publisher_domain` field provides explicit naming of the publisher. The inline resolution precondition — requiring every matched property to carry a `publisher_domain` matching the selector's addressed domains — ensures the manager has positively typed the publisher domain in the same manifest. `by_id` selectors remain federated-only and do not benefit from this path. - What does **not** satisfy this rule: an `inline_properties` selector whose inline properties omit `publisher_domain`, or a top-level `property_tags` selector whose resolved top-level properties carry no matching `publisher_domain`. If no reachable `publisher_domain` field matches the source, fallback MUST fail closed. + What does **not** satisfy this rule: an `inline_properties` selector whose inline properties omit `publisher_domain`, a top-level `property_tags` selector whose resolved top-level properties carry no matching `publisher_domain`, or a `publisher_properties` inline-resolution match where the property entry lacks `publisher_domain` (such entries MUST be excluded from the inline result set). If no reachable `publisher_domain` field matches the source, fallback MUST fail closed. The attack this protects against is implicit scoping — authorization that does not name the publisher anywhere the manager has to type the domain. Indirection through `properties[].publisher_domain` is safe because the manager has positively spelled the publisher's domain in the same manifest; the gate filters to properties whose `publisher_domain` matches the source before considering the reference. - **No success by silence**: if the manager lookup fails, treat the publisher as missing `adagents.json` (same as no fallback). @@ -493,12 +494,21 @@ Validators MUST reject any `publisher_properties[]` entry that includes both `pu The above is semantically identical to repeating the singular-form entry once per domain. Use it when every represented publisher tags inventory the same way (typical of WordPress/managed-network deployments); fall back to one entry per publisher only when the selector predicate genuinely differs. -**Fan-out resolution semantics.** Each listed domain in `publisher_domains[]` is resolved **independently and in parallel** against that publisher's `adagents.json`: +**Fan-out resolution semantics.** Each listed domain in `publisher_domains[]` is resolved **independently and in parallel**. A `publisher_properties` selector resolves against properties in one of two ways: -- Resolution is **consumer-side**, not publish-side. The manager file MUST NOT inline copies of each represented publisher's properties; consumers (validators, buyer agents indexing the file) fetch each listed publisher's own `adagents.json` to resolve the selector predicate. The manager file is the authorization declaration; the per-publisher files are the property catalog. -- If a listed publisher's `adagents.json` is unreachable (404, 5xx, timeout, fails its own validation), the selector resolves to the empty set **for that publisher only** — the entry remains valid for all other listed publishers. Consumers MUST NOT treat a single unreachable publisher as poisoning the rest of the compact entry. -- If a listed publisher's `adagents.json` carries no properties matching the predicate (no entries with the named tag), the selector resolves to the empty set for that publisher. Same partial-resolution rule applies. -- Resolution caching follows each publisher's own cache policy on their `adagents.json`, independently. A consumer SHOULD NOT extend or shorten one publisher's cache TTL based on observations of another publisher in the same compact entry. +1. **Federated resolution (default).** For each domain in `publisher_domain` or `publisher_domains[]`, fetch that publisher's `adagents.json` and apply the selector predicate against the publisher's own top-level `properties[]`. This is the trust-root path. Per-publisher error handling: if a listed publisher's `adagents.json` is unreachable (404, 5xx, timeout, fails its own validation), the selector resolves to the empty set **for that publisher only** — the entry remains valid for all other listed publishers. Consumers MUST NOT treat a single unreachable publisher as poisoning the rest of the compact entry. If a publisher's file carries no properties matching the predicate, the selector resolves to the empty set for that publisher. Resolution caching follows each publisher's own cache policy independently; a consumer SHOULD NOT extend or shorten one publisher's cache TTL based on observations of another publisher in the same compact entry. + +2. **Parent-file inline resolution (managed-network optimization).** Consumers SHOULD prefer this path when the parent file's top-level `properties[]` contains entries whose `publisher_domain` field matches the selector's addressed domains and whose other fields satisfy the selector predicate. Consumers that prefer strict federation MAY ignore the inline path entirely. Inline resolution conditions: + - Every matched property MUST carry an explicit `publisher_domain` field whose value equals one of the selector's addressed domains. A `properties[]` entry without a `publisher_domain` field MUST NOT be included in the inline resolution result set for any `publisher_properties` selector, regardless of `selection_type`. + - For `selection_type: by_tag`: the property's `tags[]` must contain at least one of the selector's `property_tags[]`. + - For `selection_type: by_id`: inline resolution is **not available**; `by_id` selectors remain federated-only even when the parent file carries inline properties. Property IDs are publisher-scoped; fanning a fixed ID set across multiple publishers would silently authorize inventory at unintended publishers. + - For `selection_type: all`: every parent-file `properties[]` entry with a `publisher_domain` matching the selector is selected. + + Inline resolution is a **per-domain optimization**: a consumer MAY use inline for domains that have matching entries in the parent file and federated for the remainder. Inline resolution MUST honor `revoked_publisher_domains[]` on the parent file; a `publisher_domain` listed as revoked resolves to the empty set for that domain regardless of whether matching properties exist in `properties[]`. Consumers that also resolve federated SHOULD cross-check the child's own `revoked_publisher_domains[]`; first match (parent or child) revokes. + + If inline and federated resolutions disagree on the same `(publisher_domain, property_id)`, the consumer MUST use the full federated property object and discard the inline object; the consumer SHOULD log the divergence as a publisher-side data-integrity warning. + + **Why this is safe.** By requiring `publisher_domain` on each matched inline property and matching against the selector's addressed domains, the inline path preserves the bilateral verification invariant: the publisher whose inventory is being authorized must be explicitly named. A manager file cannot use inline resolution to authorize inventory for a publisher it doesn't list. This is the same invariant the [`managerdomain` fallback safety rule](#safety-rules-for-this-fallback) protects. ## Authorization Qualifiers diff --git a/static/schemas/source/core/publisher-property-selector.json b/static/schemas/source/core/publisher-property-selector.json index 71dd74c7d7..d7c06ec039 100644 --- a/static/schemas/source/core/publisher-property-selector.json +++ b/static/schemas/source/core/publisher-property-selector.json @@ -9,7 +9,7 @@ "oneOf": [ { "type": "object", - "description": "Select all properties from one publisher domain, or from each publisher domain when `publisher_domains` is used.", + "description": "Select all properties from one publisher domain, or from each publisher domain when `publisher_domains` is used. Resolves against each publisher's own adagents.json (federated path) or against matching entries in the parent file's top-level `properties[]` whose `publisher_domain` matches the selector's addressed domains (inline optimization — see adagents.json fan-out resolution semantics).", "properties": { "publisher_domain": { "type": "string", @@ -76,7 +76,7 @@ }, { "type": "object", - "description": "Select properties by tag membership. With `publisher_domains`, the same `property_tags` predicate is resolved against each listed publisher's adagents.json — the common managed-network case where every represented site tags inventory with a shared label.", + "description": "Select properties by tag membership. With `publisher_domains`, the same `property_tags` predicate is resolved against each listed publisher's adagents.json (federated path) or against matching entries in the parent file's top-level `properties[]` whose `publisher_domain` matches the selector's addressed domains and whose `tags[]` contains at least one of the selector's `property_tags[]` (inline optimization — see adagents.json fan-out resolution semantics).", "properties": { "publisher_domain": { "type": "string", From 612fce5ec6d01478174903b43b668420042c9a70 Mon Sep 17 00:00:00 2001 From: "Claude Code (triage)" Date: Wed, 20 May 2026 05:26:52 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fixup:=20RFC=202119=20must=E2=86=92MUST,=20?= =?UTF-8?q?by=5Fid=20rationale,=20anchor=E2=86=92ATX=20heading,=20no=20rea?= =?UTF-8?q?l=20brand=20in=20changeset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Capitalize 'must' → 'MUST' in by_tag inline condition (RFC 2119) - Fix by_id rationale: property IDs are publisher-namespace scoped, not multi-publisher fan-out risk - Promote 'Fan-out resolution semantics' bold para to ATX heading so the #fan-out-resolution-semantics anchor in path-3 actually works - Remove real company name from changeset body (playbook prohibits) https://claude.ai/code/session_013RpTM3r22Ge29GxsqKSYHK --- .changeset/4825-adagents-inline-resolution.md | 2 +- docs/governance/property/adagents.mdx | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.changeset/4825-adagents-inline-resolution.md b/.changeset/4825-adagents-inline-resolution.md index 6ebf3b2a0f..a7e00880e9 100644 --- a/.changeset/4825-adagents-inline-resolution.md +++ b/.changeset/4825-adagents-inline-resolution.md @@ -4,7 +4,7 @@ feat(adagents): permit parent-file inline resolution of `publisher_properties` selectors when matched top-level properties carry a `publisher_domain` matching the selector's addressed domains. -Federated per-child resolution remains the default and the trust root. Inline is a per-domain optimization: consumers SHOULD prefer it when the parent file's `properties[]` contains entries whose `publisher_domain` matches the selector's `publisher_domains[]` and whose tags satisfy the predicate. The production reference for the managed-network pattern (cafemedia.com, ~6,800 represented domains) inlines properties — strict federation requires N HTTP fetches per authorization check, infeasible at managed-network scale. +Federated per-child resolution remains the default and the trust root. Inline is a per-domain optimization: consumers SHOULD prefer it when the parent file's `properties[]` contains entries whose `publisher_domain` matches the selector's `publisher_domains[]` and whose tags satisfy the predicate. Production managed-network deployments inline properties (~6,800 represented domains in a single authoritative file) — strict federation requires N HTTP fetches per authorization check, infeasible at scale. `by_id` selectors remain federated-only. Property IDs are publisher-scoped; fanning a fixed ID set across multiple publishers would silently authorize inventory at unintended publishers. diff --git a/docs/governance/property/adagents.mdx b/docs/governance/property/adagents.mdx index a7cb6abab3..dfafe4635f 100644 --- a/docs/governance/property/adagents.mdx +++ b/docs/governance/property/adagents.mdx @@ -494,14 +494,16 @@ Validators MUST reject any `publisher_properties[]` entry that includes both `pu The above is semantically identical to repeating the singular-form entry once per domain. Use it when every represented publisher tags inventory the same way (typical of WordPress/managed-network deployments); fall back to one entry per publisher only when the selector predicate genuinely differs. -**Fan-out resolution semantics.** Each listed domain in `publisher_domains[]` is resolved **independently and in parallel**. A `publisher_properties` selector resolves against properties in one of two ways: +### Fan-out resolution semantics + +Each listed domain in `publisher_domains[]` is resolved **independently and in parallel**. A `publisher_properties` selector resolves against properties in one of two ways: 1. **Federated resolution (default).** For each domain in `publisher_domain` or `publisher_domains[]`, fetch that publisher's `adagents.json` and apply the selector predicate against the publisher's own top-level `properties[]`. This is the trust-root path. Per-publisher error handling: if a listed publisher's `adagents.json` is unreachable (404, 5xx, timeout, fails its own validation), the selector resolves to the empty set **for that publisher only** — the entry remains valid for all other listed publishers. Consumers MUST NOT treat a single unreachable publisher as poisoning the rest of the compact entry. If a publisher's file carries no properties matching the predicate, the selector resolves to the empty set for that publisher. Resolution caching follows each publisher's own cache policy independently; a consumer SHOULD NOT extend or shorten one publisher's cache TTL based on observations of another publisher in the same compact entry. 2. **Parent-file inline resolution (managed-network optimization).** Consumers SHOULD prefer this path when the parent file's top-level `properties[]` contains entries whose `publisher_domain` field matches the selector's addressed domains and whose other fields satisfy the selector predicate. Consumers that prefer strict federation MAY ignore the inline path entirely. Inline resolution conditions: - Every matched property MUST carry an explicit `publisher_domain` field whose value equals one of the selector's addressed domains. A `properties[]` entry without a `publisher_domain` field MUST NOT be included in the inline resolution result set for any `publisher_properties` selector, regardless of `selection_type`. - - For `selection_type: by_tag`: the property's `tags[]` must contain at least one of the selector's `property_tags[]`. - - For `selection_type: by_id`: inline resolution is **not available**; `by_id` selectors remain federated-only even when the parent file carries inline properties. Property IDs are publisher-scoped; fanning a fixed ID set across multiple publishers would silently authorize inventory at unintended publishers. + - For `selection_type: by_tag`: the property's `tags[]` MUST contain at least one of the selector's `property_tags[]`. + - For `selection_type: by_id`: inline resolution is **not available**; `by_id` selectors remain federated-only even when the parent file carries inline properties. Property IDs are scoped to the publisher that mints them; a `by_id` selector targets IDs in that publisher's namespace, not in the manager file's inline property catalog. Inline resolution cannot reliably answer whether a given ID exists at the addressed publisher. - For `selection_type: all`: every parent-file `properties[]` entry with a `publisher_domain` matching the selector is selected. Inline resolution is a **per-domain optimization**: a consumer MAY use inline for domains that have matching entries in the parent file and federated for the remainder. Inline resolution MUST honor `revoked_publisher_domains[]` on the parent file; a `publisher_domain` listed as revoked resolves to the empty set for that domain regardless of whether matching properties exist in `properties[]`. Consumers that also resolve federated SHOULD cross-check the child's own `revoked_publisher_domains[]`; first match (parent or child) revokes.