Skip to content

fix(types)!: expose object unions without RootModel - #1084

Merged
bokelley merged 1 commit into
mainfrom
issue-1077-rootmodel-unions
Aug 25, 2026
Merged

fix(types)!: expose object unions without RootModel#1084
bokelley merged 1 commit into
mainfrom
issue-1077-rootmodel-unions

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

  • expose AccountReference, PostalArea, and SignalRef as composable public union aliases instead of generated outer RootModel wrappers
  • preserve PostalArea country/system validation and SignalRef discriminator semantics through Annotated validators
  • migrate SDK call sites and tests to concrete arms or TypeAdapter, and add an adopter type-check contract
  • document construction and validation of object unions

Fixes #1077.

Migration

Code that called AccountReference.model_validate(raw), PostalArea.model_validate(raw), or SignalRef.model_validate(raw) should use TypeAdapter(TheAlias).validate_python(raw). Construct known account arms with AccountReferenceById or AccountReferenceByNaturalKey. Wire shapes are unchanged.

Validation

  • full suite: 7268 passed, 77 skipped, 9 deselected, 1 xfailed
  • affected account/postal/signal/compatibility suites: 551 passed before the final test-only correction; focused correction suite green
  • make typecheck-all
  • make lint
  • pre-commit hooks

@bokelley
bokelley enabled auto-merge (squash) August 25, 2026 06:15
Comment thread src/adcp/types/aliases.py
# - Use when the seller resolves accounts internally from brand identity
# - Requires brand reference + operator domain

AccountReference = AccountReference1 | AccountReference2
Comment thread src/adcp/types/aliases.py
# These public names intentionally expose the schema unions directly instead
# of the generator's outer RootModel wrappers. They compose cleanly in adopter
# annotations without imposing another wrapper around their constituent arms.
PostalArea = _Annotated[
Comment thread src/adcp/types/aliases.py
]
"""Postal-area union; validate raw values with ``TypeAdapter(PostalArea)``."""

SignalRef = _Annotated[SignalRef1 | SignalRef2 | SignalRef3, Discriminator("scope")]
model_config = ConfigDict(extra="forbid")


account: AccountReference = StrictAccountReference(account_id="acct-1")
Comment thread src/adcp/types/aliases.py
# - Use when the seller resolves accounts internally from brand identity
# - Requires brand reference + operator domain

AccountReference = AccountReference1 | AccountReference2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

MUST FIX: Breaking public-surface change under a non-breaking prefix. AccountReference, PostalArea, and SignalRef were public RootModel classes; this commit reshapes them into a union alias (542) and two Annotated aliases (597, 603). That changes the type signature of three public adcp.types exports: AccountReference.model_validate(...) / AccountReference(root=...) stop working, and isinstance(x, PostalArea) / isinstance(x, SignalRef) now raise TypeError on the Annotated form. The migrated call sites in this PR (AccountReference(root=...)AccountReferenceById(...), .model_validateTypeAdapter(...)) are exactly the adopter code that breaks.

Commit is fix(types): expose object unions without RootModel with an empty body — no !, no BREAKING CHANGE: footer. release-please cuts a patch from fix:, so the break ships without a major/breaking beta signal. Per repo policy this is a high. Carry fix!: or add a BREAKING CHANGE: footer. The PR body already has the migration note; only the commit signal is missing.

@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: Request changes

Request changes — 1 blocking finding.

Blocking findings

  • src/adcp/types/aliases.py:542 — Breaking public-surface reshape (AccountReference, PostalArea, SignalRef converted from generated RootModel wrappers to composable union aliases — a type-signature change on public adcp.* exports) shipped under a non-breaking fix: prefix with no fix!:/feat!: marker and no BREAKING CHANGE: footer or migration note. Per the repo's mandatory semver-signal gate, release-please cuts a minor from fix:, so the break ships without a major. Add the breaking-change signal (fix!:/BREAKING CHANGE: footer) plus a migration note (PR body, MIGRATION_*.md, or CHANGELOG.md).

Note: the underlying refactor preserves load-bearing validation (PostalArea country/system BeforeValidator, SignalRef Discriminator("scope")) and is confirmed by new tests — the issue is purely the missing semver signal on the commit, which is a high finding here.

Blocking findings

  • src/adcp/types/aliases.py:542 — Breaking public-surface reshape (AccountReference/PostalArea/SignalRef union aliases) shipped under a non-breaking fix: prefix without fix!:/BREAKING CHANGE: footer or migration note

BREAKING CHANGE: AccountReference, PostalArea, and SignalRef are now composable union aliases. Use TypeAdapter(Alias).validate_python(...) instead of Alias.model_validate(...).
@bokelley bokelley changed the title fix(types): expose object unions without RootModel fix(types)!: expose object unions without RootModel Aug 25, 2026
@bokelley
bokelley force-pushed the issue-1077-rootmodel-unions branch from c56446b to 5029a46 Compare August 25, 2026 06:26

@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 — prior blocking finding resolved, no current findings.

The prior run flagged a [high] on src/adcp/types/aliases.py:542 — a breaking public-surface reshape (AccountReference, PostalArea, SignalRef from RootModel wrappers to composable union aliases) shipped under a non-breaking commit prefix. That finding is now resolved: the head commit is fix(types)! with a BREAKING CHANGE: footer plus migration notes in the PR body and docs, satisfying the mandatory semver signal on the public surface.

Forward-compat checks: discriminator ('scope') and PostalArea country/system validation are preserved via Annotated validators; AccountReference's two arms are disjoint under extra='forbid'. No fallback arm removed, no discriminator key changed on the wire. All migrated call sites and tests are consistent; no source site still relies on .model_validate / root= / .root for the reshaped names.

Decision-table walk: no critical/high findings (row 1 no). gated_paths is false (row 2 no). No (deleted) high-risk reasons — only (modified) (row 3 no). No medium findings at all, so rows 4, 5, 8 do not fire. Prior decision was request-changes, not escalate (row 6 no). No no-auto-approve team match (row 7 no). Falls through to row 9 → approve.

Note on review_decision=CHANGES_REQUESTED: this only forces escalate under row 2 when gated_paths is true, which it is not here. It does not otherwise block an approve. high_risk is true but only on (modified) files with zero medium-or-higher findings, so it is presumed safe per the reasons guidance.

@bokelley
bokelley merged commit 4175ed7 into main Aug 25, 2026
28 checks passed
@bokelley
bokelley deleted the issue-1077-rootmodel-unions branch August 25, 2026 06:42
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.

Reserve RootModel for value types: RootModel wrappers make extra-field policy unsettable by consumers

1 participant