Skip to content

codegen: oneOf response branches lose the root allOf ProtocolEnvelope (19 of 24 SuccessResponse aliases) #1136

Description

@KonstantinMirin

Every AdCP response schema composes core/protocol-envelope.json at its root via allOf,
so status, task_id, message, context_id, replayed, timestamp,
push_notification_config, governance_context, context, payload and adcp_error are
part of every response's contract. In adcp==6.6.0, 19 of the 24 *SuccessResponse aliases
do not inherit ProtocolEnvelope, so those eleven fields are untyped on them: a server that
sets one gets a pydantic extra, and a client that reads one gets an AttributeError.

Related to #822, which is a different bug in the same file (a stripped import producing
NameError). This one is about which classes get the base at all.

Measured against the shipped wheel

from adcp.types import ProtocolEnvelope
import adcp.types.aliases as A

for n in sorted(n for n in dir(A) if n.endswith("SuccessResponse") and not n.startswith("_")):
    c = getattr(A, n)
    if isinstance(c, type):
        print(f"{'inherits' if issubclass(c, ProtocolEnvelope) else 'MISSING '}  {n}")
MISSING   ActivateSignalSuccessResponse
MISSING   BuildCreativeSuccessResponse
MISSING   CalibrateContentSuccessResponse
inherits  CreateContentStandardsSuccessResponse
MISSING   CreateMediaBuySuccessResponse
MISSING   GetAccountFinancialsSuccessResponse
MISSING   GetBrandIdentitySuccessResponse
MISSING   GetContentStandardsSuccessResponse
MISSING   GetCreativeFeaturesSuccessResponse
MISSING   GetMediaBuyArtifactsSuccessResponse
inherits  GetProductsSuccessResponse
MISSING   GetRightsSuccessResponse
inherits  GetSignalsSuccessResponse
inherits  ListContentStandardsSuccessResponse
MISSING   LogEventSuccessResponse
MISSING   ProvidePerformanceFeedbackSuccessResponse
MISSING   SyncAccountsSuccessResponse
MISSING   SyncAudiencesSuccessResponse
MISSING   SyncCatalogsSuccessResponse
MISSING   SyncCreativesSuccessResponse
MISSING   SyncEventSourcesSuccessResponse
inherits  UpdateContentStandardsSuccessResponse
MISSING   UpdateMediaBuySuccessResponse
MISSING   ValidateContentDeliverySuccessResponse

19 missing, 5 present.

The two structurally identical inputs that generate differently

content-standards/update-content-standards-response.json and
media-buy/create-media-buy-response.json have the same shape where it matters: the root
composes ../core/version-envelope.json and ../core/protocol-envelope.json through
allOf, and neither one's Success branch references the protocol envelope itself.

update-content-standards-response.json
  root allOf: ['../core/version-envelope.json', '../core/protocol-envelope.json']
  branch UpdateContentStandardsSuccess  allOf: []      -> generated class INHERITS ProtocolEnvelope

create-media-buy-response.json
  root allOf: ['../core/version-envelope.json', '../core/protocol-envelope.json']
  branch CreateMediaBuySuccess          allOf: [...]   -> generated class does NOT

The branch-level difference runs the wrong way: CreateMediaBuySuccess carries more
composition than UpdateContentStandardsSuccess and gets less inheritance. So the
divergence is in codegen, not in the schemas.

The general rule the data fits: where a response schema has no oneOf, the alias resolves
to the root class and picks the base up from the root allOf; where it has oneOf, the
per-branch classes are generated without the root composition. UpdateContentStandards is
the one case that escapes it, which is what makes this look like a bug rather than a
deliberate exclusion.

Impact

Any field of the protocol envelope is untyped on those 19. Concretely, on the seller side:

  • status — the pinned envelope marks it required on every task response, so a seller that
    cannot set it typed has to declare the field itself.
  • replayed — AdCP L1/security.mdx idempotency rule 4 puts this on the response envelope
    of a replayed request. A boundary that sets response.replayed = True writes an extra on
    19 of 24 response types and a real field on 5, which is not a difference a caller can see.

Workaround in use

Naming ProtocolEnvelope as a second base on the local subclass:

class SyncCreativesResponse(LibrarySyncCreativesSuccess, ProtocolEnvelope):
    ...

That is additive today and becomes a no-op when the generated class carries the base, so it
deletes cleanly. Filing so it can be deleted rather than kept.

Suggested fix

Attach ProtocolEnvelope to every generated oneOf branch class of a response whose root
composes core/protocol-envelope.json, not only to the branch the driver currently treats
specially. The check is on the ROOT schema's allOf, so it needs no per-branch table.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions