Skip to content

feat(protocol): support AdCP 3.1.10 schemas - #1012

Merged
bokelley merged 3 commits into
mainfrom
release-3-1-10-retina-catalog
Aug 5, 2026
Merged

feat(protocol): support AdCP 3.1.10 schemas#1012
bokelley merged 3 commits into
mainfrom
release-3-1-10-retina-catalog

Conversation

@bokelley

@bokelley bokelley commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Updates the SDK protocol pin and bundled schemas from AdCP 3.1.8 to the signed 3.1.10 release, including the expanded Retina canonical-format registry and refreshed documentation/signing provenance. Regenerates Python models for the Trusted Match TMPX slot contract while preserving prior public helper aliases for compatibility. Experimental Trusted Match migration: provider-supplied tmpx_macros/macros are replaced by publisher-owned tmpx_slots/chunks, and IdentityMatchResponse now represents the router-to-publisher response shape. Adds codegen repairs for the renamed response model and nested publisher mapping key constraints, plus Retina projection coverage across all seven catalog sizes. Aligns projection precedence with adcontextprotocol/adcp#6217: seller-authored canonical declarations retain neutral registry dimensions but do not inherit registry slots or pixel-density constraints. Validated with the full suite (6,216 passed, 41 skipped, 9 deselected, 1 expected xfail) and all pre-commit gates.

@bokelley
bokelley marked this pull request as ready for review August 4, 2026 18:28
@aao-ipr-bot

aao-ipr-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

⚠️ Argus review could not complete

The automated review encountered an issue (possibly reached max turns, timed out, or failed to post the final gh pr review). A human reviewer should take this PR.

View workflow run

This is an automated message from the Argus AI review workflow.

aao-ipr-bot[bot]
aao-ipr-bot Bot previously approved these changes Aug 5, 2026

@aao-ipr-bot aao-ipr-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean protocol bump. Schemas, regenerated models, codegen repairs, docs, and tests all move together — the version pin and the wire shape stay in lockstep, which is the whole point of the generated-type layering.

Things I checked

  • ADCP_VERSION is the load-bearing pin, and it moved. src/adcp/ADCP_VERSION 3.1.8 → 3.1.10; README, canonical_formats/registry.py, and canonical_formats/v1_to_v2.py docstrings all track it. get_adcp_spec_version() and the registry loader (bundle_key collapses to 3.1) resolve consistently. No doc/version drift.
  • The TMPX shape change is regenerated end-to-end, not hand-edited. identity_match_response.py TmpxProviders.macroschunks, root tmpx_macros dropped, IdentityMatchResponse renamed to IdentityMatchResponseRouterPublisher. New tmpx_chunk.py, provider_identity_match_response.py, publisher_tmpx_config.py, and provider_registration.py (tmpx_macrostmpx_slots) match the schema set. schemas/cache/3.1/trusted-match/tmpx-chunk.json and publisher-tmpx-config.json are both added — every $ref resolves, no dangling refs.
  • The alias layer survives the rename. identity_match_response.py appends IdentityMatchResponse = IdentityMatchResponseRouterPublisher and retains a deprecated TmpxMacro compat model (post_generate_fixes.py:2011, restore_trusted_match_compatibility_aliases). Public imports through aliases.py don't break at import time.
  • Real codegen bug caught. fix_publisher_tmpx_mapping_key_constraints (scripts/post_generate_fixes.py) rewrites datamodel-codegen's bare StringConstraints(...) outer map key to Annotated[str, StringConstraints(...)] — without it Pydantic treats the constraint instance as a dataclass type and can't build the model. The new test importing publisher_tmpx_config exercises exactly that build path.
  • Registry change is additive. registries/v1-canonical-mapping.json 1.2.0 → 1.3.0, +353 lines of display_*_image_2x Retina entries plus the new normative "Image rendition-set exception" rule — gated on required_pixel_ratios, which only the new entries carry, so existing-format projection is unchanged.
  • Test coverage is present, contrary to first glance. test_trusted_match_validation.py moves assertions to {slot_id, value} chunks, adds test_provider_identity_match_response_accepts_tmpx_chunks, and the provider-id rejection now asserts Pydantic's propertyNames pattern error. Plus test_canonical_formats_registry.py / _v1_to_v2.py for Retina.
  • ad-tech-protocol-expert: sound — the macros→slots split moves ad-server destination names off the wire into publisher-owned config, closing the cross-provider macro-name-hijack surface; not/anyOf root guards are the correct draft-07 forbid idiom and are satisfiable. code-reviewer: no blockers.
  • The adagents.py / brand/__init__.py regen is pure timestamp-and-$schema-example-string churn (3.1.8 → 3.1.10 in embedded examples) — noted and discarded per the codegen-churn rule.

Follow-ups (non-blocking — file as issues)

  • IdentityMatchResponse shape changed under a preserved name. The alias keeps the import working, but the model no longer accepts root tmpx_macros and TmpxProviders now wants chunks, not macros — code deserializing the old #5689 shape into IdentityMatchResponse will now ValidationError. The schema self-documents this as sanctioned by x-status: experimental, and both experts signed off on breaking an experimental surface without a version hatch, so this ships. But the commit is feat(protocol): with no ! / BREAKING CHANGE: footer. Experimental-surface latitude is why this isn't a block — add a one-line migration note to the changelog (macros→chunks, IdentityMatchResponse is now the router→publisher shape) so adopters on the experimental path aren't surprised.

Minor nits (non-blocking)

  1. Mid-module import re. restore_trusted_match_compatibility_aliases / the validator patcher inject _PROVIDER_ID_PATTERN and imports below the module top in the generated identity_match_response.py — an E402-shaped artifact. Harmless as long as generated_poc/ stays lint-excluded (it is, per CLAUDE.md); worth a glance that make lint still skips it after the new file lands.

Approving on the strength of the regenerated-model/schema lockstep plus the preserved alias layer. Follow-up noted.

@aao-ipr-bot aao-ipr-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regenerates the Trusted Match/TMPX surface from AdCP 3.1.10 and lands the seller-Retina precedence fix. The wire-shape churn is a real break, but it sits entirely on an x-status: experimental surface and upstream shipped it as a patch increment — the feat: bump holds.

Things I checked

  • Breaking-change classification. TmpxProviders (extra='forbid') renames macros: list[TmpxMacro]chunks: list[TmpxChunk] with a {name,value}{slot_id,value} shape flip, root tmpx_macros is removed, and provider_registration.tmpx_macrostmpx_slots. Old-shape payloads now raise ValidationError. ad-tech-protocol-expert: breaking at the wire level, unambiguously — but x-status: experimental is confirmed present on every touched schema (schemas/cache/3.1/trusted-match/identity-match-response.json:14, provider-registration.json:5, and the three new files), and upstream itself shipped this as 3.1.8→3.1.10 patch, which is only legitimate under the experimental carve-out. feat: without ! is defensible. Not a block.
  • Import-level compat preserved. IdentityMatchResponse = IdentityMatchResponseRouterPublisher and the deprecated TmpxMacro classes are retained (unreferenced) by post_generate_fixes.py. Verified aliases.py:1998,2001 still resolves its TmpxMacro as IdentityMatchTmpxMacro / ProviderRegistrationTmpxMacro re-exports against the retained classes — the alias layer does not break.
  • Codegen post-processing, not hand-edits. The two new repair functions run inside post_generate_fixes.py:main() in the correct order — fix_trusted_match_runtime_validators (appends the indented validator into the class) before restore_trusted_match_compatibility_aliases (appends the module-level IdentityMatchResponse = ...); reversed, that's a syntax error. code-reviewer confirmed all three repair paths are idempotent (the broken/fixed needles are non-overlapping, the validator early-branches on _validate_tmpx_provider_ids in source).
  • Seller precedence (v1_to_v2.py). _SELLER_AUTHORITATIVE_PARAM_KEYS = {pixel_ratios, required_pixel_ratios, slots} strips exactly the pixel-density/slot contracts from registry_params on the seller-annotation path while width/height survive. code-reviewer enumerated the full registry param key-space ({aspect_ratio, daast_version, duration_ms_exact, height, pixel_ratios, slots, vast_version, width}) — the strip-set is correct and complete for 3.1.10. ad-tech-protocol-expert: correct reading of adcontextprotocol/adcp#6217.
  • Registry. v1-canonical-mapping.json at v1.3.0 matches the wire naming display_WxH_image_2x / display_WxH_image_1x_2x across all seven catalog sizes; test counts move 29→43 mappings, 22→36 literals, 7 structural held. _generated.py imports and __all__ are consistent for all five new names.

Follow-ups (non-blocking — file as issues)

  • Mark the TMPX surface experimental in the release notes. The feat: bump is sound because the surface is experimental, but that status lives only in schema JSON and field descriptions — src/adcp/types/registry.py exposes no programmatic x-status marker. Confirm the CHANGELOG/release entry names Trusted Match/TMPX as experimental so adopters relying on the old shape are on notice. This is the one thing that makes the minor bump defensible rather than a surprise.
  • _SELLER_AUTHORITATIVE_PARAM_KEYS is a denylist. Correct today, but a future registry that adds a new slot/pixel-density parameter leaks it into seller-authored declarations. A governance test over the registry param key-space (fail if a new key isn't classified neutral-vs-authoritative) would close the gap.
  • Root-field prohibition not enforced in Python. Both response schemas forbid tmpx_macros/tmpx_chunks at the root via not:{anyOf:[...]}; datamodel-codegen drops not and the models use extra='allow', so a stray root tmpx_macros is silently absorbed into extras rather than rejected. The SDK is more lenient than the schema on a privacy-boundary constraint — fix_trusted_match_runtime_validators already injects a provider_id validator and could close this the same way.

Minor nits (non-blocking)

  1. Dead validator in identity_match_response.py. tmpx_providers' dict key now carries StringConstraints(pattern=r'^[A-Za-z0-9_]+$', min_length=1, max_length=64), which is exactly equivalent to _PROVIDER_ID_PATTERN and fires first — the injected _validate_tmpx_provider_ids can never raise. The test even had to move its match= from "tmpx_providers keys" to "String should match pattern" to follow the change. Harmless, redundant.
  2. fix_publisher_tmpx_mapping_key_constraints fails silently on codegen drift. The broken needle hardcodes the 12-space indent and pattern, min_length, max_length arg order; if datamodel-codegen changes either, the guard no-ops and ships an un-Annotated StringConstraints outer key that Pydantic can't build. CI catches it (the import in test_trusted_match_validation.py fails loudly), but a raise on the miss beats print(... already fixed).

Approving on the strength of the experimental-status confirmation plus faithful regen against the 3.1.10 wire shape. Follow-ups noted — the CHANGELOG experimental marker is the one worth doing before the GA cut.

@bokelley
bokelley merged commit dbf3df8 into main Aug 5, 2026
28 checks passed
@bokelley
bokelley deleted the release-3-1-10-retina-catalog branch August 5, 2026 02:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant