Skip to content

chore(schemas): sync to AdCP 3.0.7 + strict drift gate#599

Merged
bokelley merged 3 commits intomainfrom
bokelley/sdk-codegen-feedback
May 8, 2026
Merged

chore(schemas): sync to AdCP 3.0.7 + strict drift gate#599
bokelley merged 3 commits intomainfrom
bokelley/sdk-codegen-feedback

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 8, 2026

Summary

  • Resyncs schemas/cache/ and src/adcp/types/generated_poc/ from the AdCP 3.0.7 dist bundle (ADCP_VERSION 3.0.5 → 3.0.7).
  • Cleans up PR #429: that PR pulled the error-code enum from static/schemas/source/ on adcontextprotocol/adcp@main (which tracks 3.1 WIP), giving us 60 codes vs the 45 every 3.0.x dist ships. The conformance test's assert len(CANONICAL_CODES) == 60 tripwire is updated to 45 with a docstring spelling out that the canonical source is the dist bundle for the pinned ADCP_VERSION, never static/schemas/source/.
  • Adds two CI strict drift checks that close the source-of-truth loophole that let test(spec-conformance): AdcpError codes against canonical error-code enum #429 land:
    1. schemas/cache/ byte-equality — fails when the committed cache differs from what sync_schemas.py produces from the dist bundle.
    2. generated_poc/ field-signature equality — fails on real semantic changes (added/removed field, class, enum member) but is invisible to datamodel-codegen's numbered-variant class-name churn (PackageUpdate1 vs PackageUpdate4) that filesystem-iteration-order produces between APFS and ext4. The previous step intentionally never failed for that exact reason; the signature check sidesteps the noise.
  • Adds scripts/diff_generated_types.py (AST-walker) and wires it into generate_types.py so every regen writes a SCHEMA_DELTAS.md listing per-class field/enum-member changes — gives consumers (e.g. salesagent's KNOWN_SCHEMA_LIBRARY_MISMATCHES) a trigger to shrink their allowlists without diffing hundreds of files.

Why one PR

The CI gate on its own would have failed any PR carrying the 60→45 enum delta (which is the regen this PR also performs). They're the same logical fix — bringing main into sync with the dist bundle and gating against future divergence — and reviewing them together makes the cause-and-effect chain easier to follow.

Background — how PR #429 leaked 3.1 state into a 3.0.x SDK

  • The dist bundle for any tagged 3.0.x release ships exactly 45 error codes.
  • static/schemas/source/enums/error-code.json on adcontextprotocol/adcp@main carries 62 — codes added during the 3.1 development window (AGENT_SUSPENDED/AGENT_BLOCKED, READ_ONLY_SCOPE/FIELD_NOT_PERMITTED, the BILLING_* and PROVENANCE_* families, CONFIGURATION_ERROR, CREDENTIAL_IN_ARGS).
  • test(spec-conformance): AdcpError codes against canonical error-code enum #429 hand-edited schemas/cache/enums/error-code.json to add 15 of those 62, edited the generated generated_poc/enums/error_code.py directly, and locked the count with assert len(CANONICAL_CODES) == 60.
  • The next clean sync reverts those hand-edits to 45 — that's not a spec deletion in a patch release (my first read), it's the bundle catching up to what was always actually shipped.

BILLING_NOT_PERMITTED_FOR_AGENT — the only one of the 17 missing codes the SDK actually raises (src/adcp/decisioning/registry.py:373) — moves to KNOWN_NON_SPEC_CODES with a TODO: drop when ADCP_VERSION >= 3.1. Collapsing it to PERMISSION_DENIED would erase the spec's documented rejected_billing / suggested_billing discriminator, so the allowlist is the right call until 3.1 ships.

Drift report

SCHEMA_DELTAS.md lands in this PR as the per-regen artifact. It's empty for the 3.0.5 → 3.0.7 step (steady state); future regens will populate it with field-level deltas.

Test plan

  • Full pytest suite (tests/ minus tests/integration/): 4230 passed, 22 skipped, 1 xfailed, 0 failed.
  • tests/test_error_code_conformance.py and tests/test_tier2_spec_conformance.py both green after the count fix and allowlist addition.
  • ruff check and mypy clean on touched files.
  • scripts/diff_generated_types.py check subcommand verified end-to-end:
    • Passes on a clean tree.
    • Fails (exit 1) on a synthetic field deletion with a clear markdown diff.
    • Passes on a synthetic class-name renumbering (signature is class-name-agnostic).
  • Verify CI's new strict steps pass on this PR (regen produces same field signatures as committed; cache matches dist bundle).
  • Confirm SCHEMA_DELTAS.md is the right surface for downstream consumers — open question whether it should be gitignored long-term or stay tracked.

🤖 Generated with Claude Code

bokelley and others added 3 commits May 8, 2026 09:44
Bumps ADCP_VERSION 3.0.5 → 3.0.7 and resyncs schemas/cache/ +
generated_poc/ from the 3.0.7 dist bundle (Sigstore-verified). Adds
scripts/diff_generated_types.py, an AST-walker that captures per-class
field/enum-member sets and produces SCHEMA_DELTAS.md on every regen so
consumers can shrink schema-mismatch allowlists without diffing
hundreds of generated files by hand.

Cleans up PR #429's source-vs-bundle confusion: that PR pulled the
error-code enum from upstream's static/schemas/source/ on `main`
(which tracks 3.1 WIP), giving us 60 codes vs the 45 every 3.0.x
dist ships. The conformance test's `assert len(CANONICAL_CODES) == 60`
tripwire is updated to 45 with a docstring spelling out that the
canonical source is the dist bundle for the pinned ADCP_VERSION,
never `static/schemas/source/`. BILLING_NOT_PERMITTED_FOR_AGENT — the
only one of the 17 dropped codes the SDK actually raises — is
allowlisted in KNOWN_NON_SPEC_CODES with a TODO pointing at the 3.1
bump; collapsing it to PERMISSION_DENIED would erase the spec's
documented `rejected_billing` / `suggested_billing` discriminator.

generate_types.py now snapshots the pre-regen tree in-process and
writes SCHEMA_DELTAS.md after a successful generation. The diff is
class-name-aware (datamodel-codegen's numbered-variant churn is
visible by design) and is a no-op for steady-state regens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the existing permissive 'Check for schema drift' step with two
strict checks that close the source-of-truth loophole that let PR #429
land 17 codes pulled from static/schemas/source/ rather than the dist
bundle.

1. schemas/cache/ byte-equality. After sync_schemas downloads the dist
   bundle for the pinned ADCP_VERSION, any diff against the committed
   cache means the cache was hand-edited or sourced from somewhere
   other than the bundle. The bundle is byte-stable so this check has
   no false-positive surface.

2. generated_poc/ field-signature equality. A new 'Snapshot committed
   types' step captures the pre-regen tree's per-class field and
   enum-member sets; after generate_types runs, diff_generated_types
   compares signatures (frozenset of frozensets per file) so
   datamodel-codegen's numbered-variant class-name churn
   (PackageUpdate1 vs PackageUpdate4 from APFS-vs-ext4 sort order)
   stays invisible. Real changes — added or removed fields, classes,
   enum members — fail the build with a markdown diff and remediation
   pointer.

The previous step intentionally never failed, citing 'numbered-variant
churn produces false positives that block release PRs for cosmetic
churn'. The signature check sidesteps that exactly: cosmetic class
renumbering preserves the multiset of field-name frozensets, so only
semantic deltas trip the gate. Hand-edits like 1a6ab9a
('rename format_ to format in FieldModel enum'), and forward-state
leaks like #429, both surface here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #599's first push committed schemas/cache/ in the raw-bundle form
(absolute $refs, $id present), missing the fix_schema_refs.py
transformation that the repo has committed since the 3.0.5 sync at
008fa3c. That broke two things in CI:

1. Strict cache byte-equality check (the new gate I added) ran AFTER
   fix_schema_refs.py during the schema-check job, comparing committed
   pre-fix shape against post-fix CI output — always false, so the
   gate fired on its own first run.

2. The storyboard runner's runtime schema_loader (continue-on-error
   but visible in CI) failed with `<urlopen error: '/schemas/3.0.7/
   core/context.json'>`. jsonschema's RefResolver joined the absolute
   $ref against a file:// base_uri to produce file:///schemas/3.0.7/
   core/context.json, which isn't keyed in the registry (registry
   keys are raw $id strings) and doesn't exist on disk. Relative refs
   like ../core/context.json would have resolved correctly.

Fix runs `python scripts/fix_schema_refs.py` against the synced cache
and recommits. Refs are now relative; $id stripped from every
schema dict (consistent with the convention 008fa3c established).
generate_types regenerated against the post-fix cache produces the
same field surface as 3.0.5 — `AdcpExtensionFileSchema` no longer
has the spurious `field_id` member that the pre-fix 3.0.7 regen
introduced (fix_schema_refs strips `$id` even when it appears as a
nested property name, which is how that member was being generated).

Also moves the strict cache check back to AFTER fix_schema_refs in
ci.yml, which is the canonical state that matches what gets
committed. Comment updated to spell out the convention so the next
sync doesn't make the same mistake.

Test plan:
- pytest: 4165 passed, 30 skipped, 1 xfailed, 0 failed.
- error-code conformance: 15/15 passed.
- ruff + mypy: clean on touched files.
- Manual: schemas/cache/media-buy/create-media-buy-async-response-
  submitted.json refs are now `../core/context.json` (relative),
  matching what was committed at 3.0.5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit 0822abb into main May 8, 2026
16 checks passed
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