Skip to content

badge status collapses for correctly-scoped agents — Degraded/Revoked for by-design behavior. #2432

Description

@LukasGoTom

Why we're filing this

We run goTom, an ad-management platform with a production AdCP seller on top. Everything we actually sell — brief-based discovery, guaranteed display/OLV, create_media_buy with package-bound creatives — passes its storyboards.

Some things we deliberately don't do: no creative library (every creative belongs to one campaign package), no third-party measurement, brief-only discovery. We declare all of that accurately in get_adcp_capabilities (mostly the schema defaults) and refuse such requests with structured, correctable errors. Per the docs, that should grade as coverage, not failure:

"If a storyboard or local test vector targets a slot the agent does not declare, the expected conformance outcome is not_applicable, not failure."get_adcp_capabilities § Capability slot gaps

3.1.x is already delivering this — requires_capability grew from 2 gates (3.0.18) to 19 capability paths (3.1.2), and two of the new gates removed false failures for us. Thank you. This issue is about the arms the rollout hasn't reached: they still grade our in-spec refusals as failed.

It matters because the badge lifecycle treats any failure as an outage (Degraded → 48 h grace → Revoked). A by-design scope gap never "recovers", so a scoped-but-healthy seller can never reach Active — and the greenest strategy available to us today would be to stop refusing: store creatives that will never serve, accept terms we won't honor. A conformance suite shouldn't reward that.

Examples we've hit

1. Creatives with no destination. Several storyboards upload creatives library-style: sync_creatives with no assignments[]. We have no library — an ad tag lives on exactly one package — so we return a correctable VALIDATION_ERROR telling the buyer to add {creative_id, package_id}. The docs already call sync_creatives conditional ("Required when the sales agent hosts a creative library", required-tasks), the SDK's supportsSyncCreatives() keys off creative.has_creative_library, and 3.1.2's state machine gates its setup phase on that flag. The baseline arms don't.
Arms: media_buy_seller/creative_sync, sales_guaranteed/creative_sync, creative_lifecycle/sync_multiple; a full 3.1.2 run also hits creative_reception, the three provenance_* sync steps, and creative/native_in_feed.

2. Measurement terms we may only reject once. The spec allows accept, reject with TERMS_REJECTED, or adjust (create_media_buy). The storyboard's first arm rewards our rejection of unworkable terms — the second arm then requires accepting the relaxed ones. We offer no third-party measurement at all, and no capability field exists to say so (checked 3.0.18 → 3.1.2; the measurement block is vendor discovery, not billing acceptance). Options today: fail forever, or accept terms we won't honor.
Arm: measurement_terms_rejected/accept_terms. The reject arm should stay universal.

3. The async escape hatch only half-works (bug). create_media_buy_async's narrative promises that sellers without the controller scenario return UNKNOWN_SCENARIO "and the runner grades this storyboard not_applicable rather than failed." Observed: only the force-step skips — the dependent chapter still runs and fails (expected "submitted", got "completed"). Same family as adcp#4226/#4228: the step-level half shipped there, the storyboard-level half is missing.

4. A buying mode we never claimed. refine_products sends buying_mode: "refine". We declare media_buy.buying_modes: ["brief"] — the schema default; buyers who check capabilities see we don't do refine. The precedent already exists: get_products_pagination_integrity is gated on buying_modes contains "wholesale". (If you consider refine core rather than optional, say so in the docs and we'll treat it as a roadmap item — our refusal is the INVALID_REQUEST the schema prescribes either way.)

Proposed solution

The machinery exists; apply it here:

  1. Creatives: gate the library-shaped arms on creative.has_creative_library — or better, bind creatives to the storyboard's own buy (creative_fate_after_cancellation already sends assignments: [{creative_id, package_id: "$context.package_id"}]). Tested beats skipped; library sellers are unaffected.
  2. Measurement: a declarable flag (e.g. media_buy.measurement_terms_supported, default false like supports_proposals) gating the accept arm only.
  3. Async: make the documented UNKNOWN_SCENARIO skip cover the storyboard, not just the step.
  4. Refine: the same buying_modes gate the wholesale sibling already has.

Not asking for: leniency on declared capabilities (those should keep failing hard), new statuses, or new machinery. Every skip stays buyer-visible.

This list is not necessarily complete

These are the arms our particular seller shape hits; other honestly-scoped sellers will hit others. The general ask is the pattern 3.1.x already established: wherever a storyboard probes a declarable capability, let the declaration drive the grading.

Offer

Production seller, full comply controller, complete runner reports available. Happy to PR the gate blocks (1, 2, 4) and the assignments-bound sync variant, and to serve as a test target.

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