Skip to content

Generated response types can't represent the protocol envelope #1678

Open

Description

@KonstantinMirin

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:

  1. 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.
  2. 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.

Activity

  1. KonstantinMirin commented on Oct 28, 2026

    @KonstantinMirin
    CollaboratorAuthor

    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.

  2. bokelley commented on Oct 29, 2026

    @bokelley
    Contributor

    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.

  3. bokelley commented on Nov 2, 2026

    @bokelley
    Contributor

    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.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions