Skip to content

feat(compat): AdapterPair pattern + v2.5 sync_creatives (stage 4)#665

Merged
bokelley merged 8 commits into
mainfrom
claude/versioned-schemas-stage-4
May 11, 2026
Merged

feat(compat): AdapterPair pattern + v2.5 sync_creatives (stage 4)#665
bokelley merged 8 commits into
mainfrom
claude/versioned-schemas-stage-4

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Re-opened with main as base after #659 (Stage 2) and #664 (Stage 3) merged — the original #661 auto-closed on base-branch deletion.

Summary

Stage 4 of the versioned-schema-validation port. Replaces the heuristic spec_compat_hooks() model with a typed per-tool adapter registry.

  • New package adcp.compat.legacy with the AdapterPair dataclass, registry (get_legacy_adapter / list_legacy_adapter_tools), and the LEGACY_ADAPTER_VERSIONS constant. Mirrors the TS SDK's src/lib/adapters/legacy/v2-5/.
  • First concrete adapter: adcp.compat.legacy.v2_5.sync_creatives. Three wire-shape coercions (bare-string format_id → structured, asset_type inference, image-without-dims → url demotion).
  • validation.envelope.SUPPORTED_WIRE_VERSIONS extends COMPATIBLE_ADCP_VERSIONS with legacy versions, so detect_wire_version accepts both shapes.
  • create_tool_caller routes legacy-versioned requests through the adapter: adapt_request runs after version detection and before current-schema validation. A legacy version with no adapter for the requested tool raises INVALID_REQUEST.

Review applied

  • code-reviewer: "Ready to push" with three nits — all addressed:
    • AdapterPair docstring now states the contract (sync + pure, no mutation, no I/O, request/response exception mapping).
    • _ensure_loaded reads from data-driven _VERSION_MODULES dict (one-line append to add a tool in Stage 5).
    • Renamed effective_versionpost_adapter_validator_version and documented the Stage 4b extension point.

Deferred to Stage 4b

Real v2.5 schema bundle. Upstream CDN doesn't ship v2.5 tarballs; the JS SDK pins to a GitHub commit SHA. Wire that fetch in a follow-up. Today the adapter validates against v3 output only.

Stage 5 will

Port the remaining v2.5 adapters (get_products, create_media_buy, update_media_buy, list_creative_formats, preview_creative) and add a DeprecationWarning on spec_compat_hooks().

🤖 Generated with Claude Code

bokelley and others added 7 commits May 11, 2026 05:35
``get_validator``, ``validate_request``, and ``validate_response`` now
accept an optional ``version=`` kwarg. ``None`` defaults to the SDK's
compile-time pin (existing behaviour, byte-identical for current call
sites). A wire-version string (``"3.0.7"``, ``"2.5"``,
``"3.1.0-beta.1"``) routes to the per-bundle-key cache laid down by
Stage 1 — each version's file index, compiled validators, and core
registry live in their own ``_LoaderState`` so cross-version traffic
doesn't share compilation state.

The dispatcher call sites are unchanged today; Stage 3 will detect the
buyer's ``adcp_major_version`` off the wire and thread it through.

Also widens ``resolve_bundle_key`` to accept bare ``MAJOR.MINOR``
(already a bundle key — matches the wire envelope's release-precision
``adcp_version`` field, so the dispatcher can pass it straight through).

Tests:
* ``test_schema_loader_per_version`` lays down a synthetic
  ``schemas/cache/2.5/`` fixture, asserts validators for the synthetic
  legacy tool and the real SDK-pin tools coexist independently, and
  pins ``version=None`` behaviour to the SDK pin.
* ``test_validation_version`` extended with the new pass-through and
  rejection cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…g tree

Review feedback on PR #659 (Stage 2). Two nits:

* Fixture used to write into the repo's real ``schemas/cache/2.5/``,
  which means CI runs against an installed wheel would silently miss
  the synthetic bundle (packaged ``_schemas/`` wins) and the assertions
  would be vacuous. Move to ``tmp_path`` + ``monkeypatch.setattr`` on
  the loader's resolver instead.
* Teardown used a sequence of ``rmdir`` calls that fail if anything else
  lands in the directories mid-run. ``shutil.rmtree(..., ignore_errors=
  True)`` is robust.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stage 3 of the versioned-schema-validation port. ``create_tool_caller``
now detects the buyer's claimed version off the request envelope and
threads it through to the per-version validator added in Stage 2.

Detection order (mirrors the TS SDK's ``applyVersionEnvelope`` and the
spec text on every ``*-request`` schema):

1. ``adcp_version`` (3.1+ release-precision string) — normalized to
   release precision (``"3.0.7"`` → ``"3.0"``); must be in
   ``COMPATIBLE_ADCP_VERSIONS`` or ``VERSION_UNSUPPORTED`` is raised.
2. ``adcp_major_version`` (legacy integer) — maps to the highest
   supported minor for that major. Unknown major raises
   ``VERSION_UNSUPPORTED`` with structured details.
3. Neither field set — falls through to the SDK pin (existing behaviour).

New module ``adcp.validation.envelope`` owns the detection logic;
``UnsupportedVersionError`` carries the wire value and supported set so
the dispatcher can echo both into the error envelope's ``details``.

Existing ``test_spec_compat_hooks`` payloads used
``adcp_major_version=4`` as a wire-shape placeholder — updated to ``3``
now that the dispatcher actually validates the field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… echo

Review feedback on PR #660.

* String ``adcp_major_version`` (e.g. ``"3"``) used to fall through to
  ``None`` and silently get SDK-pin validation. A buyer that
  JSON-stringified the field deserves a loud
  ``VERSION_UNSUPPORTED``, not a quiet behaviour swap. Same for ``< 1``
  values (below the spec's ``minimum: 1`` bound).
* ``claimed_version`` in error details used to be ``str(exc.wire_value)``,
  which converted the int field to ``"4"``. Pass through verbatim so
  buyer telemetry sees the same type they sent.
* Rename ``test_no_version_field_validator_uses_sdk_pin`` to actually
  test that claim — pass a ``ValidationHookConfig`` so the validator
  fires, and assert ``version=None`` is threaded. Split the original
  test's "no validation config means no validator runs" assertion into
  its own regression test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…T_VERSION_ENVELOPE)

Downstream-impact audit on PR #660 flagged that switching from "wire
schema accepts adcp_major_version 1-99" to "dispatcher rejects unknown
versions" in one release would break test fixtures using ``4`` as a
sentinel and any buyer claiming an unsupported version that happened to
pass schema validation.

Decouple legacy adapter routing (additive, ships now) from
version-envelope strictness (subtractive, ships under a gate).

* Default (``ADCP_STRICT_VERSION_ENVELOPE`` unset or ``"0"``): an
  unsupported wire version is logged at WARNING with a migration hint
  pointing at the env var. The dispatcher falls through to SDK-pin
  validation — same as 5.1.0 behaviour for that payload.
* Strict (``ADCP_STRICT_VERSION_ENVELOPE=1``): raise the
  ``VERSION_UNSUPPORTED`` envelope the spec prescribes, before any
  handler dispatch. This becomes the default in 5.3.

Existing strict-mode tests gain a ``strict_version_envelope`` fixture
so the spec-prescribed behaviour stays covered. New
``test_unsupported_version_permissive_falls_through`` exercises the
default-permissive path and asserts the migration hint shows up in
logs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stage 4 of the versioned-schema-validation port. Replaces the heuristic
``spec_compat_hooks()`` model with a typed per-tool adapter registry.

What changed:

* New package ``adcp.compat.legacy`` with the ``AdapterPair`` dataclass,
  registry (``get_legacy_adapter`` / ``list_legacy_adapter_tools``), and
  the ``LEGACY_ADAPTER_VERSIONS`` constant. Mirrors the TS SDK's
  ``src/lib/adapters/legacy/v2-5/``.
* First concrete adapter: ``adcp.compat.legacy.v2_5.sync_creatives``.
  Three wire-shape coercions (bare-string ``format_id`` → structured,
  ``asset_type`` inference, ``image``-without-dims → ``url`` demotion).
* ``validation.envelope.SUPPORTED_WIRE_VERSIONS`` now includes legacy
  versions, so ``detect_wire_version`` accepts both native and legacy
  claims.
* ``create_tool_caller`` routes legacy-versioned requests through the
  adapter: ``adapt_request`` runs *after* version detection and *before*
  current-schema validation, so a buggy translator surfaces as
  ``INVALID_REQUEST`` with a field-level pointer. A legacy version
  with no adapter for the requested tool raises ``INVALID_REQUEST``.
* Response side: optional ``normalize_response`` callable runs after
  current-schema validation. ``sync_creatives`` has the same response
  shape across v2.5/v3 so leaves it ``None``.

Deferred (Stage 4b): real v2.5 schema bundle. Upstream CDN doesn't
ship v2.5 tarballs; the JS SDK pins a GitHub commit SHA. Wire that
fetch in a follow-up. Today the adapter validates against the v3
output only.

Tests (25 new): registry contract, every v2.5 sync_creatives coercion
via the public adapter surface, end-to-end dispatcher routing including
missing-adapter and adapter-raises cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ader

Review feedback on Stage 4.

* AdapterPair docstring states the contract: sync + pure (no mutation),
  no I/O, request raise → INVALID_REQUEST, response raise →
  INTERNAL_ERROR.
* _ensure_loaded reads its work from _VERSION_MODULES dict — adding a
  tool to a version is a one-line append, not a control-flow change.
* Dispatcher renames effective_version → post_adapter_validator_version
  with a comment explaining how Stage 4b (real v2.5 schema bundle)
  extends the variable's role.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley force-pushed the claude/versioned-schemas-stage-4 branch from ec3de0f to 859626d Compare May 11, 2026 10:34
@bokelley bokelley merged commit 0e47dcc into main May 11, 2026
16 checks passed
@bokelley bokelley deleted the claude/versioned-schemas-stage-4 branch May 11, 2026 10:39
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