Skip to content

feat(types): provide version-scoped Pydantic extension base - #1051

Merged
bokelley merged 1 commit into
mainfrom
issue-1050-versioned-base
Aug 19, 2026
Merged

feat(types): provide version-scoped Pydantic extension base#1051
bokelley merged 1 commit into
mainfrom
issue-1050-versioned-base

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

  • add make_versioned_base(version, model_name) as a stable, public factory for subclassable version-pinned Pydantic models
  • expose real protocol fields through model_fields while retaining public nested Pydantic annotations where available
  • validate the serialized wire payload against the bundled pinned schema so excluded adopter fields never leak onto the wire
  • preserve the existing version-scoped RootModel surface unchanged

Impact

Adopters can extend a historical protocol request or response with internal Pydantic fields declared with Field(exclude=True). The generated base remains pinned to the requested protocol release, rejects fields from newer releases, and emits the canonical bundled JSON Schema.

Validation

  • uv run python -m pytest -q — 6,557 passed, 40 skipped, 9 deselected, 1 xfailed
  • make lint
  • make typecheck-all
  • make validate-generated
  • pre-commit hooks (black, ruff, mypy, adopter fixtures, bandit, repository checks)
  • smoke-created all 497 version/model combinations across bundled 3.0, 3.1, and 3.2 schemas

Closes #1050

@bokelley
bokelley marked this pull request as ready for review August 19, 2026 13:42
@bokelley
bokelley enabled auto-merge (squash) August 19, 2026 13:42
Comment on lines +455 to +458
annotation = current_annotations.get(
name,
_fallback_annotation(field_schema, schema),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium: Nested fields reuse the current SDK annotation, so version pinning is top-level only. extra="forbid" plus the top-level schema reject newer-release fields at the root (tested in test_versioned_base_enforces_version_delta_fields), but a nested object is validated solely by the pinned schema's own additionalProperties. Published AdCP schemas default additionalProperties: true (DR-0009), and the reused current-version nested model (e.g. 3.2 CreativeFilters) will happily accept a 3.2-only sub-field; with exclude_unset=True it then rides onto a 3.1 wire. The PR's "rejects fields from newer releases" guarantee holds at the root, not inside nested structures — worth stating in the docstring and covering with a nested-delta test so the scope of the pin is explicit.

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ladon verdict: Approve

Approve — adds make_versioned_base(version, model_name), a cached, subclassable Pydantic factory that pins a model to a bundled protocol schema and validates the serialized (exclude=True-stripped) payload against the pinned schema before the wire.

What I checked:

  • Fail-closed posture: extra="forbid" rejects undeclared top-level fields; Field(exclude=True) adopter fields stay out of schema validation and model_json_schema output (tested); final jsonschema pass is the wire authority.
  • Change is confined to src/adcp/types/versioned.py plus its test file — no hand-edits to generated_poc/** or _generated.py, no import-layering violations, no forward-compat/discriminated-union regressions, no credential-in-ctx_metadata concerns, no disabled tests or blanket type-ignores.
  • New public behavior ships with corresponding tests (tests/test_version_scoped_models.py).

Medium findings (1):

  • src/adcp/types/versioned.py:455 — Version pinning is top-level only; nested objects track the current SDK model, so newer-release nested fields can pass when the pinned schema allows additionalProperties. This is a documented scope caveat, not a blocking defect.

Decision path: no critical/high findings; gated_paths is false (row 2 does not apply); high_risk false; the single medium is an LLM/scope caveat, not data-loss/schema/infra; no author team gate; fewer than three medium findings. Rows 1–8 do not fire → row 9 approve.

Medium findings

  • src/adcp/types/versioned.py:455 — Version pinning is top-level only; nested fields track current SDK

@bokelley
bokelley merged commit eb64dc2 into main Aug 19, 2026
28 checks passed
@bokelley
bokelley deleted the issue-1050-versioned-base branch August 19, 2026 13:55
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.

types: provide a version-scoped Pydantic extension surface

1 participant