Skip to content

feat(server): ship built-in spec_compat_hooks() for pre-v3 / pre-4.4 buyer compatibility #646

Description

@bokelley

Problem

adcp 5.0 (PR #629, closes #614) shipped serve(pre_validation_hooks=...) — the mechanism is great, but every Python adopter that accepts pre-v3 / pre-4.4 buyers needs the same handful of spec-mandated default + shape-migration hooks. Each adopter writes (and maintains) the same code.

In bokelley/salesagent we just migrated our 273-LOC SpecDefaultsMiddleware to ~150 LOC of pre_validation_hooks. Looking at what we wrote, none of it is salesagent-specific — every line is a universal compatibility shim:

Hook What it does Why universal
get_products.buying_mode='brief' Default the field when omitted Spec says: "Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'." Every seller, by the spec text.
sync_creatives.creatives[*].format_id string → {agent_url, id} Wrap bare string format_id as the structured 4.4 reference adcp 4.4 changed format_id from string to structured. Pre-4.4 buyers send a string; any seller accepting them needs the same wrap. The default agent_url is the canonical https://creative.adcontextprotocol.org/ registry.
sync_creatives.creatives[*].assets[*].asset_type inference Infer the missing asset_type from the asset key (image/video/...) or from content/url/width+height presence adcp 4.4 added explicit asset_type to make AssetVariant discriminated; pre-4.4 buyers used the key as the implicit type. Universal compat.
sync_creatives.creatives[*].assets[*] image → url demote When asset_type='image' but width/height missing, demote to url variant The strict 4.4 image variant requires dims; pre-4.4 buyers frequently send image-shaped URLs without them. Universal compat.

We're carrying this code as if it were adopter logic. It's not — it's the SDK's compat layer for migrating buyers across spec major versions.

Proposed shape

Ship a built-in spec_compat_hooks() (or similarly named) in adcp.server that adopters can wire into pre_validation_hooks:

from adcp.server import spec_compat_hooks, serve

# Standalone:
serve(router, pre_validation_hooks=spec_compat_hooks())

# Combined with adopter-specific hooks:
serve(router, pre_validation_hooks={
    **spec_compat_hooks(),
    "create_media_buy": my_custom_hook,
})

# Selective opt-out:
serve(router, pre_validation_hooks=spec_compat_hooks(exclude={"sync_creatives"}))

Opt-in by design — adopters who want strict-4.4 rejection of pre-v3 / pre-4.4 buyers leave it off. Adopters who accept the long tail of legacy clients (most production sellers) wire it once and forget about it.

What's in / out of scope

In: The four hooks above. Each maps to a spec versioning event (pre-v3 buying_mode, pre-4.4 format_id shape, pre-4.4 asset_type discriminator, pre-4.4 image dim requirement).

Out: Adopter-specific defaulting (e.g. salesagent's removed account: auth-chain placeholder — that was an adopter bug, not a spec migration). Adopters write those themselves via the same pre_validation_hooks mechanism, layered on top.

Why this matters beyond us

These hooks are exactly the kind of glue every adopter has to write when migrating from a pre-4.4 SDK to a 5.0 SDK. Shipping them in the SDK:

  1. Saves each adopter ~150 LOC of mechanical-but-spec-sensitive code.
  2. Centralises the canonical agent_url (https://creative.adcontextprotocol.org/), the asset-type inference table, the image-dim demotion rule — adopters who roll their own will silently diverge.
  3. Provides a single PR target when the spec adds the next pre-major-version compat shim (adcp 6.0 → 7.0 someday) instead of every adopter re-discovering it.

Source

Our core/spec_default_hooks.py is what we just wrote — happy to send the contents as a starter PR if there's interest in shipping it directly. The file is intentionally pure-Python (no transport / framework deps) so dropping it into adcp.server should be drop-in.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions