This is a suggestion and a question rather than a bug report. The second half asks a specification-design question that an SDK cannot answer alone.
Suggested typing for the consumer side
core/canonical-format-kind.json makes the enum asymmetric on purpose:
Open-enum semantics (normative). … Consumer SDKs MUST treat this enum as open at parse time: an unknown format_kind value MUST be retained as-is on the in-memory object (not silently dropped or rewritten to "custom") and MUST NOT cause the surrounding payload to fail validation. … The producer-side enum stays closed …
The generated field is the bare enum, so a canonical promoted in a later minor release fails validation of the whole payload. Reproduced against adcp==6.6.0:
CreativeAsset2.model_validate({"creative_id": "c", "name": "n", "assets": {},
"format_kind": "a_future_canonical_from_3_2"})
# ValidationError: [{'type': 'enum', ...}]
CanonicalFormatKind already subclasses StrEnum, so a union costs little and round-trips correctly:
TypeAdapter(CanonicalFormatKind | str).validate_python("image") # -> CanonicalFormatKind.image ('image')
TypeAdapter(CanonicalFormatKind | str).validate_python("future_x") # -> 'future_x'
A known value keeps its enum member, and because the member subclasses str, comparison and serialization behave as before. An unknown value survives as a str, which is what the MUST requires. The change applies wherever the generator emits format_kind: creative-asset, product-format-declaration, creative-manifest, and package-request.
This typing change is not validation. It makes the SDK forward-compatible, and it constrains no producer. That distinction leads to the question.
Open question: where the producer-side constraint belongs
The producer rule in the specification is strict. Sellers "MUST NOT mint ad-hoc format_kind values — use format_kind: "custom" with format_shape + format_schema", and both format_shape and format_schema are "REQUIRED when format_kind: "custom"; otherwise MUST be absent."
Nothing enforces either rule. Measured against ProductFormatDeclaration:
ProductFormatDeclaration.model_validate({"format_kind": "custom", "params": {}})
# ACCEPTED - carries neither format_shape nor format_schema
ProductFormatDeclaration.model_validate({"format_kind": "image", "params": {}, "format_shape": "roadblock"})
# ACCEPTED - carries format_shape on a non-custom kind
So the SDK is strict in the one place the specification mandates openness, and permissive in the place the specification constrains producers. Fixing the typing leaves the second half open.
Here is why I am asking rather than filing a bug. A closed enum that ships a custom escape, whose own requirements go unvalidated, combines the costs of both models: consumers cannot treat the enum as exhaustive, and producers get no enforcement that custom carries the schema that makes it interpretable. If custom is a first-class extension point, then the conditional that makes it usable — format_shape plus format_schema — is the part that most needs validating. If custom is not an extension point, then the twelve canonicals plus the promotion queue in #3666 make a simpler contract.
Do you intend the seller's own boundary to validate this conditional rather than an SDK? If so, please state that normatively: no tool in the chain grades the conditional, so each seller lands somewhere different. I am happy to move this half to adcontextprotocol/adcp if that repository suits it better. The typing suggestion stands on its own either way.
This is a suggestion and a question rather than a bug report. The second half asks a specification-design question that an SDK cannot answer alone.
Suggested typing for the consumer side
core/canonical-format-kind.jsonmakes the enum asymmetric on purpose:The generated field is the bare enum, so a canonical promoted in a later minor release fails validation of the whole payload. Reproduced against
adcp==6.6.0:CanonicalFormatKindalready subclassesStrEnum, so a union costs little and round-trips correctly:A known value keeps its enum member, and because the member subclasses
str, comparison and serialization behave as before. An unknown value survives as astr, which is what the MUST requires. The change applies wherever the generator emitsformat_kind:creative-asset,product-format-declaration,creative-manifest, andpackage-request.This typing change is not validation. It makes the SDK forward-compatible, and it constrains no producer. That distinction leads to the question.
Open question: where the producer-side constraint belongs
The producer rule in the specification is strict. Sellers "MUST NOT mint ad-hoc
format_kindvalues — useformat_kind: "custom"withformat_shape+format_schema", and bothformat_shapeandformat_schemaare "REQUIRED whenformat_kind: "custom"; otherwise MUST be absent."Nothing enforces either rule. Measured against
ProductFormatDeclaration:So the SDK is strict in the one place the specification mandates openness, and permissive in the place the specification constrains producers. Fixing the typing leaves the second half open.
Here is why I am asking rather than filing a bug. A closed enum that ships a
customescape, whose own requirements go unvalidated, combines the costs of both models: consumers cannot treat the enum as exhaustive, and producers get no enforcement thatcustomcarries the schema that makes it interpretable. Ifcustomis a first-class extension point, then the conditional that makes it usable —format_shapeplusformat_schema— is the part that most needs validating. Ifcustomis not an extension point, then the twelve canonicals plus the promotion queue in #3666 make a simpler contract.Do you intend the seller's own boundary to validate this conditional rather than an SDK? If so, please state that normatively: no tool in the chain grades the conditional, so each seller lands somewhere different. I am happy to move this half to
adcontextprotocol/adcpif that repository suits it better. The typing suggestion stands on its own either way.