Skip to content

feat(media-buy): unified product, proposal, and live-buy action assessment #2664

Description

@bokelley

Summary

Implement the SDK layer for AdCP's product → proposal → live-MediaBuy action model introduced by adcontextprotocol/adcp#6750 and the accompanying 3.2 schema changes.

The buyer should be able to answer three different questions without manually joining protocol objects:

  1. Product allowed_actions[]: could this product support the change?
  2. Proposal commercial_terms.change_terms[]: what did the accepted deal promise?
  3. MediaBuy available_actions[]: what is executable now?

The seller should have one resolver that derives the third surface from the first two plus current status and caller authorization, rather than constructing three drifting declarations.

Protocol compatibility

  • available_actions[].terms_ref shipped in AdCP 3.1 and remains an opaque compatibility field through 3.x.
  • AdCP 3.2 adds change_term_id as the unambiguous link to accepted_proposal.commercial_terms.change_terms[].term_id.
  • Never reinterpret an arbitrary 3.1 terms_ref as a proposal-term identity.
  • When a 3.2 compatibility payload emits both as aliases, require equality.
  • 3.2 adds seller_managed, proposal allowed_statuses, and portable budget/flight/package-count/effective-timing constraints.
  • The entire absence of proposal change_terms means legacy_unknown; an explicit array means omitted actions are not negotiated.

Buyer API

Add a cohesive action assessment API (exact naming is maintainers' choice) with a discriminated result such as:

type ActionAvailability =
  | { status: 'available_now'; action: MediaBuyAction; route: MediaBuyTask; mode: MediaBuyActionMode }
  | { status: 'wrong_status'; action: MediaBuyAction; allowedStatuses: MediaBuyStatus[] }
  | { status: 'not_negotiated'; action: MediaBuyAction }
  | { status: 'unsupported_by_product'; action: MediaBuyAction }
  | { status: 'currently_unavailable'; action: MediaBuyAction }
  | { status: 'legacy_unknown'; action: MediaBuyAction };

Expected public behavior:

  • Assess product actions without presenting advisory templates as promises.
  • Assess proposal rights, including status scope, mode, SLA, typed constraints, opaque conditions, and contract reference.
  • Explain current action availability by joining accepted terms with the live buy.
  • Preserve unknown when seller-only conditions or missing legacy information prevent a decision.
  • Extend existing mutation decomposition/preflight to check portable constraints when enough current state is available.
  • Route an executable action to control_media_buy, refine_proposals, or sync_creatives from canonical action metadata.
  • Treat seller_managed as the standard async task lifecycle, not as a disclosed approval/HITL process.
  • Continue using revision/idempotency safeguards and refresh from ACTION_NOT_ALLOWED.currently_available_actions after races.

Seller API

Provide a resolver/builder that:

  • materializes proposal change terms from product templates only when the seller explicitly accepts them as binding;
  • computes current available_actions[] from accepted change terms, MediaBuy status, account authorization, governance delegation, and seller policy;
  • guarantees an available action cannot exceed the accepted term's mode, SLA, status scope, or typed constraints;
  • emits change_term_id for 3.2 and an explicit 3.1 compatibility projection where requested;
  • validates unique action/term identities and action/task/constraint compatibility;
  • uses the existing canonical state-machine helpers rather than creating approval-specific MediaBuy statuses.

Tests

Add fixtures and tests for:

  • AdCP 3.1.19 terms_ref input and valid_actions[] fallback;
  • a released 3.2 beta bundle without the new fields;
  • current 3.2 change_term_id, seller_managed, status scope, and each typed constraint kind;
  • both alias fields equal, both unequal, and a 3.1 opaque value that coincidentally resembles a term ID;
  • product-supported but not negotiated;
  • negotiated but wrong status;
  • negotiated and available immediately;
  • negotiated seller-managed action sent through the wrong route;
  • opaque conditions producing currently_unavailable/unknown rather than false approval;
  • stale revision race followed by a current action-set echo;
  • active → paused projections and terminal-state empty projections;
  • mixed multi-action requests where one action is blocked;
  • generated types, ESM/CJS exports, and tree-shakeable browser use.

Package and run the new media_buy_seller/change_rights_state_projection compliance scenario and the expanded compact lifecycle scenario from the matching AdCP bundle.

Acceptance criteria

  • A buyer can render “possible / promised / available now” and explain a blocked action without reimplementing protocol joins.
  • A seller can derive correct action surfaces through one SDK resolver.
  • Legacy 3.1 remains readable, but no compatibility heuristic grants authority.
  • JavaScript API behavior has parity requirements documented for adcp-client-python.

Upstream: adcontextprotocol/adcp#6750

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