feat(types): provide version-scoped Pydantic extension base - #1051
Conversation
| annotation = current_annotations.get( | ||
| name, | ||
| _fallback_annotation(field_schema, schema), | ||
| ) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 andmodel_json_schemaoutput (tested); final jsonschema pass is the wire authority. - Change is confined to
src/adcp/types/versioned.pyplus its test file — no hand-edits togenerated_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
Summary
make_versioned_base(version, model_name)as a stable, public factory for subclassable version-pinned Pydantic modelsmodel_fieldswhile retaining public nested Pydantic annotations where availableRootModelsurface unchangedImpact
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 xfailedmake lintmake typecheck-allmake validate-generatedCloses #1050