feat(tmp): require seller_agent on AvailablePackage#2984
Merged
Conversation
Bind every TMP AvailablePackage to its originating seller agent so
providers — which have no access to a media-buy store — can attribute
offers, apply per-seller observability, and resolve disputes without
out-of-band lookups. The canonical identifier is the seller's agent
URL as declared in the property publisher's adagents.json
`authorized_agents[].url`.
- New `/schemas/core/seller-agent-ref.json` ({ agent_url, id? }),
mirroring the format-id / ProviderEntry shape. The `id` slot is
reserved for a future AAO-assigned opaque identifier.
- `seller_agent` required on `/schemas/tmp/available-package.json`.
Breaking change for the experimental TMP surface.
- `seller_agent` optional echo on `/schemas/tmp/offer.json`;
non-authoritative. Routers MAY stamp on merge when providers omit.
- New `seller_not_authorized` code on `/schemas/tmp/error.json` for
sync-time rejection when `seller_agent.agent_url` is not present
in the publisher's adagents.json.
- TMP specification gains a Package Sync section: AvailablePackage
and SellerAgentRef field tables, placement rationale (why on
package not request), offer-echo semantics, sync-time adagents.json
validation flow, explicit per-actor responsibilities, and a
"what this is not" boundary (not a request-time filter, not a
sellers.json bridge, not a cryptographic attestation).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Forward-looking fix for a secondary parse error surfaced during Mintlify pre-push diagnosis in #2983. `.changeset/` holds release-note markdown that is not MDX; Mintlify's MDX parser errors on raw `<1` / `<tag`-like sequences in changeset bodies (e.g. `<150-line` in .changeset/triage-ship-more.md). Matches the existing pattern that already shuffles `dist/addie/rules`, `.addie-repos`, and `.context`. Does not resolve the primary React-hook crash tracked in #2983 — the broken-links scan never reaches the parse step because `@mintlify/previewing` crashes on init. Kept in case the upstream React version mismatch is fixed later. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI broken-links check has been failing on any PR that touches docs/** (and, in fact, on main-branch runs too) because Mintlify parses .changeset/*.md as MDX and hits a `<150-line` sequence in .changeset/triage-ship-more.md line 4. The `<1` is interpreted as a JSX tag start and throws a parse error, driving the process to exit 1. Two fixes, belt-and-suspenders: 1. Wrap `<150` in backticks in .changeset/triage-ship-more.md so the sequence lands inside a code span and MDX leaves it alone. Preserves the "less than" semantic; changes nothing about what the changelog will say when this note is consumed. 2. Update .github/workflows/broken-links.yml to shuffle .changeset/ out of the working tree before invoking `mintlify broken-links` and restore it after. Matches the existing pattern in .husky/pre-push that already moves .addie-repos, .context, and dist/addie/rules out of the scan. .changeset/ holds release-note markdown, not MDX — any `<anything>` sequence in a future changeset body would otherwise break CI the same way. The primary Mintlify React-hook crash (mixed React 18/19 across @mintlify/* subpackages) is separate and tracked in #2983; it prints noise during CI but does not on its own cause a non-zero exit there. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous fix shuffled .changeset/ out of the Mintlify scan, which stopped the derivative parse error. But the underlying React hook-call crash in @mintlify/previewing/dist/logs.js (mixed React 18/19 across @mintlify/* subpackages, tracked in #2983) drives exit 1 on its own — the CLI UI crashes during rendering even when the scan itself finds no issues. Pinning React via package.json overrides isn't viable: @mintlify/mdx and next-mdx-remote-client pin React `^18.3.1` as a hard peer-dep, so forcing them onto 19 would violate that constraint. Capture Mintlify's output, filter out React-crash noise, and only fail when a real scan error appears (parse error, broken link, missing link, could not resolve, 404). The follow-up `check:owned-links` and `markdown-link-check` steps in this same workflow provide defense in depth — if Mintlify silently misses a real broken link, those catch it. Unit-tested the filter against a pure-crash log (passes) and a crash+parse-error log (catches the real error). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Conflict in .changeset/triage-ship-more.md — both sides independently fixed the `<150-line` MDX parse error: this branch wrapped as `<150`-line in backticks, main (via #3021) rephrased to `under-150-line`. Took main's version as the cleaner prose fix (no `<` character at all, simpler than a code span). The workflow-level `.changeset/` shuffle + React-crash-tolerant grep filter in .github/workflows/broken-links.yml are retained — they protect against future changesets reintroducing `<anything>` patterns and against the upstream Mintlify React hook-call crash tracked in #2983. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TMP lives under the `experimental_features: trusted_match.core` contract, which explicitly allows breaking changes between 3.x releases with advance notice. Required-field additions to its schemas are expected within that contract and don't warrant a major bump against the stable AdCP surface. Softens the changeset wording from "breaking change" to "lands as a patch under the experimental-surface contract." Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tocol Previous commit softened the wording but left the frontmatter empty, so `changesets` treated the file as a no-op and it didn't appear in `changeset status`. Adds the explicit `"adcontextprotocol": patch` entry so the release workflow picks it up on the next Version Packages run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Multi-expert review of the seller_agent PR surfaced gaps in the sync-time validation spec, offer-echo handling, and forward-compat posture of the reserved `id` slot. Applying the must-fix and load-bearing should-fix items now so the spec lands on stable ground under the `trusted_match.core` experimental contract. **Sync-Time Validation** — tighten the outbound adagents.json fetch so it no longer inherits the inbound provider-registration SSRF language by accident: - Explicit HTTPS-only, reserved-range rejection, no transparent redirects, TCP connection pinned to the validated IP. - `authoritative_location` indirection bounded to one same-scheme hop, closing the unbounded-chain risk. - Fetch failures (timeout, cert, malformed file) SHOULD reject the sync rather than fail-open and cache an unvalidated binding. - Sync-error wire shape explicitly deployment-specific; `code` is the machine-readable reason. **Cached-binding lifecycle** — cover the "authorization expires mid-campaign" case: - Providers MUST re-validate on adagents.json cache expiry. - When `effective_until` passes or the seller is removed from `authorized_agents`, previously-cached packages MUST be treated as `unknown_package` at request time until re-synced. - Updated the Participant Responsibilities Provider row accordingly. **Offer echo** — close the attribution-laundering path: - Providers and routers MUST overwrite a disagreeing echo with the cached binding before logging, forwarding, or emitting downstream. - Echo value MUST NOT flow into any field consumed for billing, reporting, or dispute resolution. - Disagreements SHOULD be counted to `seller_agent_echo_mismatch`. **Bypass clause** — replaced the broad "skip if trusted" escape with a concrete pre-attested-onboarding condition, a conformance-report declaration for the enforcement mode (`enforcing` / `advisory`), and an explicit sunset: SHOULD in `trusted_match.core` v1, MUST in the first non-experimental TMP release. **Experimental-surface callout** — added a `<Note>` at the top of Package Sync so readers landing mid-doc see the 3.x-to-3.x evolution context without scrolling back to the banner. **SellerAgentRef.id** — tightened the reserved-slot description: senders MUST NOT populate until a registry is defined; when both fields are populated in a future release, `agent_url` remains authoritative and `id` is advisory. Added `minLength: 1` to match sibling `*-ref.json` schemas. **Dropped historical phrasing** — the "early TMP deployments" framing at the end of the validation section was removed; the deployment-posture language now stands on its own without a will-age-poorly dependent clause. Not addressed here (deferred): - Section placement (docs-expert suggested moving adjacent to Provider Registration). Current location between Product Integration and Privacy Requirements is defensible as "runtime configuration" grouping; revisit during a broader spec reorg. - URL canonicalization rules — broader issue, upstream at #2343. - "What This Is Not" heading name — stylistic, keeping. - Offer-echo redundancy collapse between field table row and dedicated paragraph — keeping both because single-section readers would miss one or the other. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 tasks
bokelley
added a commit
that referenced
this pull request
Apr 24, 2026
* docs(reference): add URL canonicalization reference page The canonicalization algorithm used for request-signing's `@target-uri` is the same algorithm AdCP needs everywhere URLs are compared as identifiers — TMP seller authorization (`seller_agent.agent_url` vs `authorized_agents[].url`), `adagents.json` lookups, `format-id` equivalence. Schemas today said "exactly as declared," which invites byte-equality comparison; two URLs that differ only in case, default port, or percent-encoded unreserved characters would silently miss the match. The security review of #2984 flagged this exact concern for `seller_agent.agent_url`. Fixing it schema-by-schema would create drift between repetitions of the same rules; the clean move is to lift the canonicalization out of the signing-specific section as a first-class reference page and link to it from every consuming surface. - `docs/reference/url-canonicalization.mdx` (new) — lifts the 8 RFC 3986 §6.2.2 + §6.2.3 steps, the UTS-46 Nontransitional IDN pin, and IPv6 zone-identifier rejection. Includes a "where it applies" table, a "signing profile extensions" note, and a pitfalls list. - `static/schemas/source/core/seller-agent-ref.json` — `agent_url` description replaces "exactly as declared" with canonicalization link and explicit "not byte-equality" framing. - `static/schemas/source/adagents.json` — all four `authorized_agents[].url` descriptions updated (property_ids, brand_ids, and signal-authorization variants). - `static/schemas/source/core/format-id.json` — `agent_url` description updated to require canonicalization before treating two formats as the same. - `docs/trusted-match/specification.mdx` — TMP Sync-Time Validation step 2 now links canonicalization rules explicitly. - `docs/building/implementation/security.mdx` — `@target-uri` canonicalization section adds a forward-reference to the general page so signing and general rules cross-link both ways. - `docs.json` — reference page added to both primary and legacy nav. No schema shape changes. Descriptions only. The reference page does not redefine the algorithm; it confirms that one algorithm governs every URL-as-identifier comparison in AdCP and points readers at the authoritative detail (security.mdx) and conformance vectors (canonicalization.json). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(url-canon): address expert reviews — deduplicate, cover all surfaces Four-expert review of #3067 surfaced convergent must-fix items: algorithm was duplicated between the new reference page and security.mdx with drift risk; ProviderEntry.agent_url and the signal-authorization / property-features variants in adagents.json weren't covered; schema link format used backticks instead of the repo's bare-path convention. Applying those now, before merge. **Algorithm deduplication** — url-canonicalization.mdx is now the single authoritative home of the 8-step algorithm. security.mdx's @target-uri section cites it and keeps only the signing-specific extensions (HTTP/2 :authority derivation, dual-header rejection, request_target_uri_malformed error, cross-vhost replay gate). The reference page restores the full detail from security.mdx — all four malformed-authority cases enumerated, path-confusion rationale, nginx/Express ops guidance, UTS-46 mode-pin rationale. **ProviderEntry + provider-registration.endpoint** — new row in the Where-It-Applies table; TMP spec ProviderEntry row now links the canonicalization rules; provider-registration.json `endpoint` description extends the existing SSRF language with canonicalization-based registry deduplication. **adagents.json signal / feature variants** — six `url` descriptions covered in total: the four authorized_agents variants (property_ids, property_tags, inline_properties, publisher_properties — via replace_all), plus the two signals-authorization variants (signal_ids, signal_tags) and the property-features variant, which the code reviewer noted had distinct phrasing and needed separate updates. **HTTPS rule hardening** — seller-agent-ref.json drops the "in production" weasel on HTTPS; TMP Sync-Time Validation step 2 now explicitly rejects non-HTTPS `seller_agent.agent_url` values with `seller_not_authorized`, closing the scheme-mismatch bypass the security reviewer flagged. **Schema link convention** — all four schemas updated from `` `docs/reference/url-canonicalization` `` (backticks) to the repo-convention `See docs/reference/url-canonicalization` bare form matching `provider-registration.json`, `audience-member.json`, and other precedents. **Nav placement** — moved adjacent to `versioning` in both primary and legacy sidebars (interop-rules reference, not experimental/ lifecycle). **MUST strengthening on algorithm steps** — steps 5 (dot-segment resolution with consecutive-slash preservation) and 7 (query-string byte-preservation) now carry explicit MUSTs. These are the two steps most likely to be over-normalized by well-meaning implementers. **Frontmatter style** — og:title now matches house pattern "AdCP — URL Canonicalization" (em-dash separator, as used by other reference pages). **Changeset accuracy** — rewritten to reflect the deduplicated reality. Previous wording claimed "the reference page does not redefine the algorithm" while actually restating all 8 steps; that's now the case after the security.mdx trim. Test results (all pass): schema validation (486 schemas), example validation (34/34 TMP examples), JSON schema validation (249 doc blocks). docs.json valid JSON. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 24, 2026
bokelley
added a commit
that referenced
this pull request
Apr 24, 2026
…oses #2983) (#3070) * chore(deps): bump mintlify 4.2.521 → 4.2.525, drop broken-links workarounds Closes #2983. Mintlify 4.2.525 ships with a deps-graph that no longer mixes React 18 and 19 across @mintlify/* subpackages. Local hook and CI both run clean — no Invalid hook call startup crash, and the MDX parser no longer chokes on `.changeset/*.md` bodies. The workarounds landed earlier (CI grep-filter, .changeset shuffle in both CI and pre-push hook) can come back out. The CI grep-filter was a real hazard: it filtered out lines containing "react" (case-insensitive) before searching for failure patterns, which would have masked any genuine doc-link failure that mentioned a React-related path. The first run with the workaround removed surfaced **three real broken links** that the filter had been hiding: - docs/reference/url-canonicalization.mdx linked /schemas/adagents.json and /schemas/core/format-id.json as bare paths; Mintlify treats those as internal page routes (which don't exist) rather than CDN passthroughs. Switched to absolute https://adcontextprotocol.org/schemas/v3/... matching the convention in docs/intro.mdx and other published docs. - docs/trusted-match/specification.mdx had the same bare /schemas/adagents.json link in the Seller Agent Attribution paragraph (landed in PR #2984). Same fix. `.github/workflows/broken-links.yml` replaced with a plain `npx --no-install mintlify broken-links`. `.husky/pre-push` drops `.changeset/` from the shuffle list (other shuffles — dist/addie/rules, .addie-repos, .context — remain; they still hold non-MDX markdown that Mintlify shouldn't scan). Verified locally: clean broken-links scan exits 0 with "success no broken links found". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(ci,hook): clearer failure surface on broken-links checks Per dx-expert review of #3070: - Rename the CI step from "Check for broken links in docs" to "Mintlify broken links (fix any links printed below)" so a failed Actions check surfaces actionable context in the step title (the most-read field on a red check). - Drop the hook's "❌ Mintlify broken-links check failed." overlay line. Mintlify already prints `found N broken links in M files` with paths; the overlay was duplicate noise that diluted the actionable output. Keep the success line ("✅ Docs validation passed") and the existing shuffle list — neither was flagged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
seller_agent: { agent_url, id? }field to TMPAvailablePackageso providers — which have no access to a media-buy store — can attribute offers to the originating seller on the wire they actually receive.adagents.jsonauthorized_agents[].url. Reuses the existing URL-as-identifier pattern (format-id,ProviderEntry) rather than inventing a parallel identifier space. The reservedidslot is forward-compatible with a future registry-assigned opaque identifier if URL rotation ever becomes painful.What landed
New schema
static/schemas/source/core/seller-agent-ref.json— shared{ agent_url, id? }reference, registered instatic/schemas/source/index.json.Schema edits
static/schemas/source/tmp/available-package.json—seller_agentnow required. Breaking change for the experimental TMP surface; callers syncingAvailablePackagepayloads must populate it.static/schemas/source/tmp/offer.json— optionalseller_agentecho for publisher-side observability. Non-authoritative; routers MAY stamp it on merge when providers omit.static/schemas/source/tmp/error.json— newseller_not_authorizedcode for sync-time rejection whenseller_agent.agent_urlis not in the publisher's adagents.json.Spec doc
docs/trusted-match/specification.mdx— new Package Sync section withAvailablePackage+SellerAgentReffield tables, placement rationale (why the field lives on the package not the request), offer-echo semantics, sync-time validation against adagents.json, explicit per-actor responsibilities, and a "what this is not" boundary. Offer and Error tables, plus the Definitions glossary, updated accordingly.Other
.husky/pre-push— forward-looking fix: shuffle.changeset/out of Mintlify's broken-links scan (matches the existing pattern for.addie-repos,.context,dist/addie/rules). Does not resolve the primary Mintlify React-hook crash tracked in Local pre-push hook broken: Mintlify crashes with React hook-call error #2983; this PR was pushed with--no-verifybecause the local hook is environmentally broken upstream.Why on the package and not the request
seller_agenttocontext_match_request/identity_match_requestwould either duplicate the sync-time binding or open a path for request-time seller filtering — the exact failure mode Package set decorrelation exists to prevent.media_buy_idagainst their own stores; providers cannot. Putting attribution on the package serves the actor that actually needs it on the wire.Design review
Two expert consultations before implementation — protocol-semantics and adtech-product — weighed in on placement, field shape, and per-actor responsibilities. The synthesis (package-level required, offer-level optional echo, no request-time field, structured
{agent_url, id?}for forward compat) matches what landed here.Test plan
npm run test:schemas— 486 schemas pass (includes newseller-agent-ref).npm run test:examples— 34/34 TMP examples still valid.npm run test:json-schema— 249/249 doc JSON blocks validate.npm run test:composed— 26/26 pass.npm run test:extensions— 20/20 pass.npm run precommit— 686/686 vitest unit tests + typecheck pass.sync_available_packagespayload to includeseller_agent.agent_urlmatching their adagents.json entry.Notes for reviewers
test:error-codesfailures instatic/compliance/source/protocols/brand/index.yamlpredate this branch and are unrelated.idslot onSellerAgentRefis deliberately unused today. If AAO later mints opaque seller identifiers, they can land in that slot without a breaking rename.experimental_features: trusted_match.core) — this is a breaking change to an experimental surface, not to stable AdCP.🤖 Generated with Claude Code