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.
Every AdCP response schema composes
core/protocol-envelope.jsonat its root viaallOf,so
status,task_id,message,context_id,replayed,timestamp,push_notification_config,governance_context,context,payloadandadcp_errorarepart of every response's contract. In
adcp==6.6.0, 19 of the 24*SuccessResponsealiasesdo not inherit
ProtocolEnvelope, so those eleven fields are untyped on them: a server thatsets 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
19 missing, 5 present.
The two structurally identical inputs that generate differently
content-standards/update-content-standards-response.jsonandmedia-buy/create-media-buy-response.jsonhave the same shape where it matters: the rootcomposes
../core/version-envelope.jsonand../core/protocol-envelope.jsonthroughallOf, and neither one's Success branch references the protocol envelope itself.The branch-level difference runs the wrong way:
CreateMediaBuySuccesscarries morecomposition than
UpdateContentStandardsSuccessand gets less inheritance. So thedivergence is in codegen, not in the schemas.
The general rule the data fits: where a response schema has no
oneOf, the alias resolvesto the root class and picks the base up from the root
allOf; where it hasoneOf, theper-branch classes are generated without the root composition.
UpdateContentStandardsisthe 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 thatcannot set it typed has to declare the field itself.
replayed— AdCPL1/security.mdxidempotency rule 4 puts this on the response envelopeof a replayed request. A boundary that sets
response.replayed = Truewrites an extra on19 of 24 response types and a real field on 5, which is not a difference a caller can see.
Workaround in use
Naming
ProtocolEnvelopeas a second base on the local subclass: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
ProtocolEnvelopeto every generatedoneOfbranch class of a response whose rootcomposes
core/protocol-envelope.json, not only to the branch the driver currently treatsspecially. The check is on the ROOT schema's
allOf, so it needs no per-branch table.