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:
- 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.
- Measurement: a declarable flag (e.g.
media_buy.measurement_terms_supported, default false like supports_proposals) gating the accept arm only.
- Async: make the documented
UNKNOWN_SCENARIO skip cover the storyboard, not just the step.
- 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.
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_buywith 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:3.1.x is already delivering this —
requires_capabilitygrew 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 asfailed.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_creativeswith noassignments[]. We have no library — an ad tag lives on exactly one package — so we return a correctableVALIDATION_ERRORtelling the buyer to add{creative_id, package_id}. The docs already callsync_creativesconditional ("Required when the sales agent hosts a creative library", required-tasks), the SDK'ssupportsSyncCreatives()keys offcreative.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 hitscreative_reception, the threeprovenance_*sync steps, andcreative/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; themeasurementblock 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 returnUNKNOWN_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_productssendsbuying_mode: "refine". We declaremedia_buy.buying_modes: ["brief"]— the schema default; buyers who check capabilities see we don't do refine. The precedent already exists:get_products_pagination_integrityis gated onbuying_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 theINVALID_REQUESTthe schema prescribes either way.)Proposed solution
The machinery exists; apply it here:
creative.has_creative_library— or better, bind creatives to the storyboard's own buy (creative_fate_after_cancellationalready sendsassignments: [{creative_id, package_id: "$context.package_id"}]). Tested beats skipped; library sellers are unaffected.media_buy.measurement_terms_supported, defaultfalselikesupports_proposals) gating the accept arm only.UNKNOWN_SCENARIOskip cover the storyboard, not just the step.buying_modesgate 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.