Skip to content

feat: make canonical creatives the primary SDK contract #35

Description

@bokelley

Context

The TypeScript SDK is making canonical creatives the contract of its next major release in adcontextprotocol/adcp-client#2391. Adopting the modern SDK should mean application code uses format_kind, format_options, and format_option_refs, while {agent_url, id} creative identity is confined to wire adapters and explicitly named migration APIs.

The Java SDK should establish the same boundary across its core, server, Spring Boot, Reactor, Mutiny, and Kotlin-facing modules so language/runtime choice does not preserve legacy creative identity in normal adopter code.

Required behavior

  • Add canonical creative-bearing models and make them the default public types for products, packages, creatives, media-buy requests/responses, delivery responses, and server handler payloads.
  • Move raw {agent_url, id} creative shapes behind explicit Legacy* model names and explicitly named legacy client/server methods or packages.
  • Make primary discovery and write methods canonical across get_products, create_media_buy, update_media_buy, sync_creatives, list_creatives, media-buy reads, and creative-delivery reads.
  • Preserve raw wire access for migration/conformance tooling through a conspicuous legacy surface; hard-deprecate it for removal with AdCP 4.0.
  • Apply one capability matrix consistently:
    • AdCP 3.0: legacy wire; upgrade reads and downgrade writes.
    • AdCP 3.1: the version alone is not canonical evidence; use media_buy.features.canonical_creatives, then request-local schema evidence where available, otherwise fail closed unless an unambiguous legacy projection exists.
    • AdCP 3.2+: canonical by contract; canonical_creatives: false is a capability error.
  • Keep catalog lookup URL-sensitive, including explicit historical AAO aliases; never match an unrelated owner by ID alone.
  • Add an inbound legacy-format converter for seller-owned formats and a separate canonical-to-legacy resolver for persisted canonical custom formats.
  • Omit a wholly unmappable product from the primary canonical product list, retain partially mapped products, and surface a sanitized portable advisory plus SDK diagnostics; never guess or omit without an advisory.
  • Fail before transport for ambiguous, incomplete, invalid, or canonical-only downgrades.
  • Preserve canonicalization context across synchronous calls, CompletionStage, Reactor, Mutiny, polling, async completion, and webhooks without unbounded task-state retention.
  • For server/Spring APIs, normalize legacy inbound requests before adopter handlers and project responses back to the caller's negotiated dialect.

Java-specific design notes

  • Keep generated wire DTOs separate from canonical application models rather than exposing both legacy and canonical identity as peer fields on one normal DTO.
  • Model canonical format declarations as a sealed hierarchy or equivalent discriminator-safe API.
  • Use distinct functional interfaces for legacy→canonical conversion and canonical→legacy resolution.
  • Expose a typed projection exception containing operation, affected creative/product, and reason; preserve it consistently across synchronous, Reactor, and Mutiny surfaces.
  • Ensure Kotlin consumers see the same canonical nullability and sealed-type behavior.

Compatibility and release

  • Treat this as a major Java SDK change and publish an RC before GA.
  • Include a migration guide with canonical replacements and explicit legacy escape hatches.
  • Add cross-language fixtures shared with the TypeScript, Python, and Go SDKs, covering known AAO formats, historical aliases, custom formats, persistence boundaries, and all-unmappable discovery.
  • Add transition tests for discovery → selection → serialization → create/update/sync → async/webhook response across the supported async adapters.

Acceptance criteria

  • A normal Java/Kotlin client or server integration can complete the creative lifecycle without handling agent_url, format_id, or format_ids in application code.
  • Legacy AdCP 3.0/3.1 peers still interoperate through deterministic adapters or return actionable projection errors.
  • Seller-specific custom formats have tested bidirectional conversion hooks.
  • Core, server, Spring Boot, Reactor, Mutiny, and Kotlin modules expose consistent canonical semantics.
  • Public documentation clearly distinguishes the SDK major version from the negotiated AdCP protocol version.
  • RC validation uses the same cross-language transition fixture and expected diagnostics as feat: enforce canonical creative SDK boundaries adcp-client#2391.

References

Cross-SDK tracking

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions