You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Generated response types can't represent the protocol envelope #1678
adcontextprotocol/adcp#8547 fixes the root cause in the spec. Task response schemas now compose core/protocol-envelope.json, closed ones included, so every response accepts and declares the envelope fields: status, task_id, context_id, message, timestamp, replayed, adcp_error, push_notification_config, governance_context, adcp_version and adcp_major_version. A 3.2.x backport with the same design is coming. Background: adcontextprotocol/adcp#8518.
Because the old schemas didn't compose the envelope, the generated types can't represent a conforming response:
Generated types are missing envelope fields.ListProductsResponse in src/adcp/types/v32.pyi:29289 has replayed, context and ext, because those were copied into the schema by hand. It has no status, adcp_version, task_id, timestamp or message. Typed code can't read or set fields that every conforming response carries. The same applies to every response type generated from a schema that didn't compose the envelope.
Validation rejects conforming responses.VersionedSchemaModel (src/adcp/types/versioned.py:228) validates against the exact bundled schema. Against the 3.2.7 bundle, a conforming list_products response that carries status and the adcp_version echo fails validation.
What needs to change:
Regenerate types from the composed schemas once #8547 and the 3.2.x backport land. Check that codegen follows root allOf into protocol-envelope (and through it into version-envelope), so every response type carries the envelope fields.
Rebundle the 3.2 schemas from the backport release.
Review the places that pop status from responses (src/adcp/server/mcp_tools.py:3946, src/adcp/compat/versions.py:961). They're only correct when converting to wire versions where status meant something else. For 3.1 and later, status is required on every response.
A correction to the "check that codegen follows allOf" item: it already does. GetMediaBuysResponse composes the envelope, and its generated type carries every envelope field (status, adcp_version, task_id, timestamp, …). So the missing fields come entirely from the schemas, and regenerating from adcontextprotocol/adcp#8547 (and the 3.2.x backport, once released) should be enough. The status pops in mcp_tools.py and compat/versions.py still need checking: they're only correct when the served release is 3.0.
Readiness check for the regeneration this issue needs, measured on 2026-10-29 by running scripts/generate_types.py against adcp#8547's schema tree (fix/compose-protocol-envelope-on-task-responses at dc66f234, overlaid onto a scratch copy of the 3.3.0-beta.1 cache with bundle-style refs; nothing committed).
Codegen follows the composition. With #1705 (the #1680 fix) applied, the post-generation passes run to completion and the staged tree has class ProtocolEnvelope(AdcpVersionEnvelope) and class ListProductsResponse1(AdcpResponse, ProtocolEnvelope); 117 response arms inherit ProtocolEnvelope and none the old (AdcpVersionEnvelope, ProtocolEnvelope) pair. The envelope fields (status, task_id, context_id, message, timestamp, replayed, adcp_error, push_notification_config, governance_context, adcp_version, adcp_major_version) reach every response type through the base, so point 1 resolves by regeneration alone once the composed bundle is released.
One blocker surfaces at export consolidation. The new schema set changes the import graph so that core/creative_assets.py evaluates asset_union.AssetVariant in its AdCPRootModel[list[...]] base while core/assets/asset_union.py is still initializing (error_details/__init__ → core/__init__ → asset_union → video_brief_asset → creative/__init__ → get_creative_delivery_response → core/creative_variant → creative_manifest → creative_assets). AttributeError: partially initialized module ... has no attribute 'AssetVariant'. Today's 3.3.0-beta.1 tree does not hit it from any of those entry points, so it is a regeneration-time fix: either order the domain __init__ imports so asset_union completes first, or make the root model's element reference lazy.
Point 2 (VersionedSchemaModel validating against the exact bundled schema) resolves with the rebundle from the 3.2.x backport / 3.3 release; no SDK change until then.
The status pops (server/mcp_tools.py legacy-adapter path and compat/versions.py downgrade step) are both version-gated: the first fires only for a legacy adapter, the second only when converting downward to a wire version where status meant something else and records a ConversionLoss. They stay correct for 3.1+ and need no change for this issue.
Status on next (10.0.0b4, pinned to AdCP 3.3.0-beta.3), measured on 2026-11-02 at 7edba16d:
1. Generated types carry the envelope. The signed 3.3.0-beta.3 bundle composes core/protocol-envelope.json on every task response, and ProtocolEnvelope composes AdcpVersionEnvelope. Regenerating from it produced class ListProductsResponse1(AdcpResponse, ProtocolEnvelope) and the same for every task response root; 164 of the 172 *Response names in adcp.types are ProtocolEnvelope subclasses, and the 8 that are not are not task responses (AccountResponse, PaginationResponse, WebhookChallengeResponse, the signed brand-claim payloads, ...). tests/test_response_status_gating.py::test_generated_responses_inherit_the_protocol_envelope_fields pins status, task_id, timestamp, adcp_version, adcp_major_version and replayed on ListProductsResponse1 / DeclineProposalsResponse1, and tests/test_protocol_envelope_inheritance.py holds every response arm to the envelope. The import cycle I reported above (creative_assets ↔ asset_union) is fixed by making the domain package roots lazy (#1784, PR #1797).
2. Validation accepts a conforming response. The versioned validators follow the 3.3 bundle, so list_products with status and the adcp_version echo validates. Whether a release carries status on the two formerly compact tools is read from that release's own response schema (adcp.validation.uses_task_status_envelope(tool, version) asks whether it composes core/protocol-envelope.json), not from the version string, so the 3.2.x backport bundle needs no SDK change once it is bundled.
3. The status pops in server/mcp_tools.py and compat/versions.py are gated by that same predicate: under a release whose list_products / decline_proposals root is closed and does not compose the envelope, the defaulted completed is not sent (and the 3.3 → 3.2 conversion drops it as a recorded loss); nothing is stripped for any other tool or any composing release (test_server_emits_status_only_where_the_served_release_has_it, test_conversion_down_drops_the_status_a_3_2_root_refuses).
What remains is on the 3.2 line: main (SDK 9, pinned 3.2.7) gets the envelope only when the spec's 3.2.x backport bundle is released and bundled. #1753 carries the codegen repairs (#1705, #1741) to main so that regeneration can run there.
Activity
KonstantinMirin commentedon Oct 28, 2026
A correction to the "check that codegen follows
allOf" item: it already does.GetMediaBuysResponsecomposes the envelope, and its generated type carries every envelope field (status,adcp_version,task_id,timestamp, …). So the missing fields come entirely from the schemas, and regenerating from adcontextprotocol/adcp#8547 (and the 3.2.x backport, once released) should be enough. Thestatuspops inmcp_tools.pyandcompat/versions.pystill need checking: they're only correct when the served release is 3.0.bokelley commentedon Oct 29, 2026
Readiness check for the regeneration this issue needs, measured on 2026-10-29 by running
scripts/generate_types.pyagainst adcp#8547's schema tree (fix/compose-protocol-envelope-on-task-responsesatdc66f234, overlaid onto a scratch copy of the 3.3.0-beta.1 cache with bundle-style refs; nothing committed).Codegen follows the composition. With #1705 (the #1680 fix) applied, the post-generation passes run to completion and the staged tree has
class ProtocolEnvelope(AdcpVersionEnvelope)andclass ListProductsResponse1(AdcpResponse, ProtocolEnvelope); 117 response arms inheritProtocolEnvelopeand none the old(AdcpVersionEnvelope, ProtocolEnvelope)pair. The envelope fields (status,task_id,context_id,message,timestamp,replayed,adcp_error,push_notification_config,governance_context,adcp_version,adcp_major_version) reach every response type through the base, so point 1 resolves by regeneration alone once the composed bundle is released.One blocker surfaces at export consolidation. The new schema set changes the import graph so that
core/creative_assets.pyevaluatesasset_union.AssetVariantin itsAdCPRootModel[list[...]]base whilecore/assets/asset_union.pyis still initializing (error_details/__init__→core/__init__→asset_union→video_brief_asset→creative/__init__→get_creative_delivery_response→core/creative_variant→creative_manifest→creative_assets).AttributeError: partially initialized module ... has no attribute 'AssetVariant'. Today's 3.3.0-beta.1 tree does not hit it from any of those entry points, so it is a regeneration-time fix: either order the domain__init__imports soasset_unioncompletes first, or make the root model's element reference lazy.Point 2 (
VersionedSchemaModelvalidating against the exact bundled schema) resolves with the rebundle from the 3.2.x backport / 3.3 release; no SDK change until then.The
statuspops (server/mcp_tools.pylegacy-adapter path andcompat/versions.pydowngrade step) are both version-gated: the first fires only for a legacy adapter, the second only when converting downward to a wire version wherestatusmeant something else and records aConversionLoss. They stay correct for 3.1+ and need no change for this issue.fix(codegen): generate the composed envelope layout of adcp#8547 (#1741)
bokelley commentedon Nov 2, 2026
Status on
next(10.0.0b4, pinned to AdCP 3.3.0-beta.3), measured on 2026-11-02 at7edba16d:1. Generated types carry the envelope. The signed 3.3.0-beta.3 bundle composes
core/protocol-envelope.jsonon every task response, andProtocolEnvelopecomposesAdcpVersionEnvelope. Regenerating from it producedclass ListProductsResponse1(AdcpResponse, ProtocolEnvelope)and the same for every task response root; 164 of the 172*Responsenames inadcp.typesareProtocolEnvelopesubclasses, and the 8 that are not are not task responses (AccountResponse,PaginationResponse,WebhookChallengeResponse, the signed brand-claim payloads, ...).tests/test_response_status_gating.py::test_generated_responses_inherit_the_protocol_envelope_fieldspinsstatus,task_id,timestamp,adcp_version,adcp_major_versionandreplayedonListProductsResponse1/DeclineProposalsResponse1, andtests/test_protocol_envelope_inheritance.pyholds every response arm to the envelope. The import cycle I reported above (creative_assets↔asset_union) is fixed by making the domain package roots lazy (#1784, PR #1797).2. Validation accepts a conforming response. The versioned validators follow the 3.3 bundle, so
list_productswithstatusand theadcp_versionecho validates. Whether a release carriesstatuson the two formerly compact tools is read from that release's own response schema (adcp.validation.uses_task_status_envelope(tool, version)asks whether it composescore/protocol-envelope.json), not from the version string, so the 3.2.x backport bundle needs no SDK change once it is bundled.3. The
statuspops inserver/mcp_tools.pyandcompat/versions.pyare gated by that same predicate: under a release whoselist_products/decline_proposalsroot is closed and does not compose the envelope, the defaultedcompletedis not sent (and the 3.3 → 3.2 conversion drops it as a recorded loss); nothing is stripped for any other tool or any composing release (test_server_emits_status_only_where_the_served_release_has_it,test_conversion_down_drops_the_status_a_3_2_root_refuses).What remains is on the 3.2 line:
main(SDK 9, pinned 3.2.7) gets the envelope only when the spec's 3.2.x backport bundle is released and bundled. #1753 carries the codegen repairs (#1705, #1741) tomainso that regeneration can run there.