Skip to content

refactor(schemas): per-bundle-key schema cache layout (stage 1 of versioned validation)#658

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

refactor(schemas): per-bundle-key schema cache layout (stage 1 of versioned validation)#658
bokelley merged 3 commits into
mainfrom
claude/versioned-schemas-stage-1

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 11, 2026

Summary

Stage 1 of porting the JS SDK's versioned-schema-validation model to Python (replacing the heuristic spec_compat_hooks() approach). The on-disk cache moves from schemas/cache/... to schemas/cache/{bundle_key}/... so multiple AdCP spec versions can coexist.

bundle_key collapses stable releases to MAJOR.MINOR (3.0.73.0) and keeps prereleases exact (3.1.0-beta.1). Mirrors resolveBundleKey() in the TypeScript SDK.

Zero behavior change — the loader and every script derive the bundle key from the SDK-pinned ADCP_VERSION, so today's single-version flow resolves schemas/cache/3.0/ exactly the same way the old code resolved schemas/cache/.

For reviewers pulling this branch locally

Stale unversioned files from the old layout could coexist with the new 3.0/ subdir on an existing dev checkout. Clean first:

rm -rf schemas/cache/ && python scripts/sync_schemas.py

(or just delete the unversioned siblings; the moved files arrived via git mv and are at the right paths after checkout.)

Subsequent stages (separate PRs)

  • Stage 2. get_validator(tool, direction, version=None) per-version arg + per-bundle-key state map.
  • Stage 3. Wire-level version detection (adcp_major_version / adcp_version) + VERSION_UNSUPPORTED envelope.
  • Stage 4. First legacy adapter (v2.5.sync_creatives) wired through the dispatcher.
  • Stage 5. Remaining adapters + spec_compat_hooks() deprecation.

What changed in code

  • New adcp.validation.version.resolve_bundle_key() with tests.
  • _resolve_schema_root takes an optional bundle_key (defaults to SDK pin).
  • scripts/sync_schemas.py writes to schemas/cache/{bundle_key}/, deriving the key from target_version (the SDK pin) so the latest.tgz fallback path stays valid.
  • scripts/generate_types.py, fix_schema_refs.py, post_generate_fixes.py read from the per-bundle-key subdirectory.
  • Tests with hardcoded schemas/cache/... paths updated to derive the bundle key the same way production code does.

Review notes addressed

  • code-reviewer: latent crash on latest.tgz fallback (resolve_bundle_key("latest") rejection). Fixed by using target_version for the bundle key, with a regression test pinning the call site.
  • python-expert: packaging/wheel layout safe; added dev-checkout cleanup note above.

Test plan

  • pytest tests/ -q --ignore=tests/integration — 4397 passed
  • pytest tests/test_validation_version.py tests/test_sync_schemas.py — 26 tests including the fallback regression
  • ruff check + mypy src/adcp/
  • Schemas physically moved to schemas/cache/3.0/ (git mv preserves history)
  • Loader resolves the new layout end-to-end

🤖 Generated with Claude Code

bokelley and others added 3 commits May 11, 2026 04:27
Move the schema cache from ``schemas/cache/...`` to
``schemas/cache/{bundle_key}/...`` so multiple AdCP spec versions can
coexist on disk. ``bundle_key`` is derived from a version string via
``resolve_bundle_key()``: stable releases collapse to ``MAJOR.MINOR``
(``3.0.7`` → ``3.0``); prereleases keep their full identifier
(``3.1.0-beta.1``). Mirrors ``resolveBundleKey()`` in the TypeScript SDK.

Stage 1 of the versioned-schema-validation port. Zero behavior change:
the loader and every script derive the bundle key from the SDK-pinned
``ADCP_VERSION`` so today's single-version flow resolves
``schemas/cache/3.0/`` exactly the same way the old code resolved
``schemas/cache/``.

Per-version arg on the loader, wire-level version detection, and the
legacy v2.5 adapter registry land in subsequent stages.

Changes:

* Move every file under ``schemas/cache/*`` into ``schemas/cache/3.0/*``
  (git mv so history follows).
* New ``adcp.validation.version.resolve_bundle_key()`` with tests.
* ``adcp.validation.schema_loader._resolve_schema_root`` now takes an
  optional ``bundle_key`` (defaults to the SDK pin) and walks
  ``schemas/cache/{bundle_key}/`` plus the packaged copy at
  ``_schemas/{bundle_key}/``.
* ``scripts/sync_schemas.py`` writes to
  ``schemas/cache/{bundle_key}/``.
* ``scripts/generate_types.py``, ``scripts/fix_schema_refs.py``,
  ``scripts/post_generate_fixes.py`` read from the per-bundle-key
  subdirectory.
* Tests with hardcoded ``schemas/cache/...`` paths updated to derive
  the bundle key the same way the production code does.
* ``test_sync_schemas`` updated for ``replace_cache_from_bundle``'s new
  ``bundle_key`` arg.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review feedback on PR #658. When ``fetch_bundle_with_fallback`` 404s on
the pinned bundle and falls back to ``latest.tgz``,
``effective_version`` is the literal string ``"latest"`` —
``resolve_bundle_key("latest")`` would crash.

Use ``target_version`` (the SDK pin) as the bundle-key input. The cache
is keyed by what the loader looks up, not by what the server happened to
return — writing latest's contents under the target's bundle key is the
behaviour adopters rely on (a pre-release dev snapshot still validates
under the SDK's pinned version).

Adds a regression test on the call site so a future refactor that
re-introduces ``resolve_bundle_key(effective_version)`` fails loudly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Stage 1 commit added ``from adcp.validation.version import
resolve_bundle_key`` to four codegen/sync scripts. That triggers
``adcp/__init__.py``, which eagerly imports the generated Pydantic
models. ``post_generate_fixes.py`` runs *during* regeneration — at
that point the models are freshly generated but not yet post-fixed,
so importing them raises ``TypeError: Value 'reuse' for discriminator
'source' mapped to multiple choices`` and exits 1. CI caught this on
the ``Validate schemas are up-to-date`` job.

Load ``version.py`` directly via ``importlib.util.spec_from_file_location``
in all four scripts — no package init, no chicken-and-egg with
mid-regeneration models. The single source of truth for the helper
stays in ``src/adcp/validation/version.py``; unit tests at
``tests/test_validation_version.py`` exercise it through the normal
package path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit 5aee245 into main May 11, 2026
16 checks passed
@bokelley bokelley deleted the claude/versioned-schemas-stage-1 branch May 11, 2026 08:49
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