You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Creative-library storyboards run (and fail) against sellers declaring has_creative_library:false — gating leaks via tool auto-registration and schema defaults (both 3.1.2 and 3.1.5 packs) #2401
Four media_buy_seller storyboards execute their sync_creatives steps against a seller that explicitly declares it has no creative library (creative.has_creative_library: false, media_buy.features.inline_creative_management: true, and sync_creatives omitted from tools/list). Instead of skipping, all four run and fail with UNSUPPORTED_FEATURE.
None of the four gate on has_creative_library, and the gates they do declare are each satisfied by something other than an affirmative capability claim from the agent.
Reproduces identically on the 3.1.2 and 3.1.5 compliance packs — this is not a recent regression in the pack, though one of the four causes does appear to trace to #2278 (see below).
media_buy.propagation_surfaces is not declared at all.
Expected
Storyboards whose steps call sync_creatives should skip (capability_unsupported / missing_tool) for an agent declaring creative.has_creative_library: false.
Actual
Four storyboards run and fail. Both packs produce identical results — 119 steps, 36 pass / 4 fail / 79 skip, with zero per-step status differences between 3.1.2 and 3.1.5:
.some() means a storyboard listing five required tools runs when the agent advertises any one of them. creative_fate_after_cancellation requires [get_products, create_media_buy, update_media_buy, sync_creatives, list_creatives]; the agent advertises the first three, so the gate passes and the storyboard proceeds to call the tool it does not have. The failure message (agent does not advertise any of [...]) confirms the OR intent is deliberate, but it makes required_tools unusable as a capability precondition — it cannot express "this storyboard needs sync_creatives".
If OR semantics are intentional, the field is misnamed and there is no working way to require a specific tool; if a storyboard needs all of them, this should be .every().
2. Tool discovery reports a tool the agent omits from tools/list
The runner's own observation reports 15 tools including sync_creatives and list_creatives, while the agent's tools/list returns the 14 above without sync_creatives:
Verified directly against the running agent — a raw tools/list JSON-RPC call returns 14 entries with no sync_creatives. I haven't traced the mechanism (the runner may union tools/list with capability-implied or SDK-registered tool names), but the effect is that the discovered set includes a tool the agent deliberately withholds. This compounds #1: even under .every() semantics the gate would still pass.
3. requires_capability is satisfied by a JSON Schema default the agent never declared
dependency_impairment and dependency_impairment_cardinality gate on:
The agent never declares propagation_surfaces. But resolveCapabilityPathForGate falls through to getSchemaDefaultByPath when the path is absent, and get-adcp-capabilities-response.json defines:
Because the agent declares somemedia_buy block, schemaDefaultShouldApply returns true, the runner substitutes ["snapshot"], and contains: "snapshot" passes. The storyboard-level gate therefore never short-circuits, setup runs, and sync_creatives fails.
The pack's own comment flags this behavior (dependency_impairment.yaml:25):
So this looks like an unintended consequence of #2278: before that change these storyboards graded not_applicable for agents that don't declare propagation_surfaces; now the schema default satisfies the gate and the run fails instead of skipping. A schema default describes what a field means when omitted — it is not an affirmative capability claim, and treating it as one inverts the fail-safe direction of every requires_capability gate that targets a defaulted field.
Worth noting the spec already draws exactly this distinction for a neighbouring field. From the creative_approval_mode description in the same schema:
When absent, approval behavior is legacy-unspecified; runners SHOULD NOT treat omission as an affirmative auto-approval claim.
4. pending_creatives_to_start conflates auto-approval with having a creative library
It gates only on media_buy.creative_approval_mode == auto_approve. A conformant inline-only auto-approve seller matches that gate legitimately, but the supply_creatives phase invokes sync_creatives with no inline fallback.
Its inline-only counterpart inline_creatives_without_sync (gated on media_buy.features.inline_creative_management == true) passes against this same agent, so the vocabulary to express this correctly already exists — this scenario just doesn't use it.
Suggested fixes
Scenario authoring — add an explicit creative.has_creative_library gate to every storyboard whose steps call sync_creatives:
(for the two dependency_impairment storyboards this needs to compose with the existing propagation_surfaces gate)
pending_creatives_to_start: add the same gate, or branch supply_creatives to the inline-creative path when inline_creative_management == true and has_creative_library == false, mirroring inline_creatives_without_sync.
Runner — two behavioral fixes, both of which change fail-open to fail-closed:
Do not apply JSON Schema defaults when resolving a requires_capability path. Absent should mean absent. If default-materialization is needed for some gates, make it opt-in per predicate (e.g. use_schema_default: true) rather than the global fallback.
Either fix required_tools to .every(), or add a genuinely conjunctive field so a storyboard can state that it needs a specific tool. Also worth reconciling the discovered-tool set with what tools/list actually returns (Fix Fly.io deployment timeout issues #2).
Since both the 3.1.2 and 3.1.5 packs are affected, a scenario-side fix likely wants backporting across the 3.1.x cache line rather than landing only on tip.
Impact
Any spec-conformant inline-only seller — has_creative_library: false plus inline_creative_management: true, precisely the configuration inline_creatives_without_sync exists to validate — cannot obtain a clean media_buy_seller run on any 3.1.x pack. Four scenarios report fail where they should report skip, and 26 further steps cascade to prerequisite_failed.
Because requires_capability currently fails open on any defaulted field, the blast radius is wider than these four storyboards: every gate whose target path carries a JSON Schema default is affected the same way.
Summary
Four
media_buy_sellerstoryboards execute theirsync_creativessteps against a seller that explicitly declares it has no creative library (creative.has_creative_library: false,media_buy.features.inline_creative_management: true, andsync_creativesomitted fromtools/list). Instead of skipping, all four run and fail withUNSUPPORTED_FEATURE.None of the four gate on
has_creative_library, and the gates they do declare are each satisfied by something other than an affirmative capability claim from the agent.Reproduces identically on the 3.1.2 and 3.1.5 compliance packs — this is not a recent regression in the pack, though one of the four causes does appear to trace to #2278 (see below).
Environment
@adcp/sdk@12.1.1(pack 3.1.5) and@adcp/sdk@12.0.4(pack 3.1.2)@adcp/sdk@12.0.4adcp storyboard run <url> media_buy_seller --allow-http --compliance-version <3.1.2|3.1.5>Agent's declared capabilities (relevant subset)
{ "supported_protocols": ["media_buy", "creative"], "media_buy": { "features": { "inline_creative_management": true }, "creative_approval_mode": "auto_approve" }, "creative": { "has_creative_library": false, "supports_compliance": false, "supports_generation": false, "supports_transformation": false } }tools/listreturns 14 tools and does not includesync_creatives:media_buy.propagation_surfacesis not declared at all.Expected
Storyboards whose steps call
sync_creativesshould skip (capability_unsupported/missing_tool) for an agent declaringcreative.has_creative_library: false.Actual
Four storyboards run and fail. Both packs produce identical results — 119 steps, 36 pass / 4 fail / 79 skip, with zero per-step status differences between 3.1.2 and 3.1.5:
pending_creatives_to_start/sync_creativerequires_capability: media_buy.creative_approval_mode == auto_approvecreative_fate_after_cancellation/sync_creative_with_assignmentrequired_tools: [get_products, create_media_buy, update_media_buy, sync_creatives, list_creatives](norequires_capability)dependency_impairment/sync_creativerequires_capability: {path: media_buy.propagation_surfaces, contains: "snapshot"}dependency_impairment_cardinality/sync_two_creativesAll four fail with the same error:
The failures also cascade: 26 of the 79 skips are
prerequisite_faileddownstream of these four setup steps.Root causes
Four distinct gating gaps, each independently sufficient.
1.
required_toolsis OR-evaluated, not ANDrunner.mjs:1238:.some()means a storyboard listing five required tools runs when the agent advertises any one of them.creative_fate_after_cancellationrequires[get_products, create_media_buy, update_media_buy, sync_creatives, list_creatives]; the agent advertises the first three, so the gate passes and the storyboard proceeds to call the tool it does not have. The failure message (agent does not advertise any of [...]) confirms the OR intent is deliberate, but it makesrequired_toolsunusable as a capability precondition — it cannot express "this storyboard needssync_creatives".If OR semantics are intentional, the field is misnamed and there is no working way to require a specific tool; if a storyboard needs all of them, this should be
.every().2. Tool discovery reports a tool the agent omits from
tools/listThe runner's own observation reports 15 tools including
sync_creativesandlist_creatives, while the agent'stools/listreturns the 14 above withoutsync_creatives:Verified directly against the running agent — a raw
tools/listJSON-RPC call returns 14 entries with nosync_creatives. I haven't traced the mechanism (the runner may uniontools/listwith capability-implied or SDK-registered tool names), but the effect is that the discovered set includes a tool the agent deliberately withholds. This compounds #1: even under.every()semantics the gate would still pass.3.
requires_capabilityis satisfied by a JSON Schema default the agent never declareddependency_impairmentanddependency_impairment_cardinalitygate on:The agent never declares
propagation_surfaces. ButresolveCapabilityPathForGatefalls through togetSchemaDefaultByPathwhen the path is absent, andget-adcp-capabilities-response.jsondefines:Because the agent declares some
media_buyblock,schemaDefaultShouldApplyreturns true, the runner substitutes["snapshot"], andcontains: "snapshot"passes. The storyboard-level gate therefore never short-circuits,setupruns, andsync_creativesfails.The pack's own comment flags this behavior (
dependency_impairment.yaml:25):So this looks like an unintended consequence of #2278: before that change these storyboards graded
not_applicablefor agents that don't declarepropagation_surfaces; now the schema default satisfies the gate and the run fails instead of skipping. A schema default describes what a field means when omitted — it is not an affirmative capability claim, and treating it as one inverts the fail-safe direction of everyrequires_capabilitygate that targets a defaulted field.Worth noting the spec already draws exactly this distinction for a neighbouring field. From the
creative_approval_modedescription in the same schema:4.
pending_creatives_to_startconflates auto-approval with having a creative libraryIt gates only on
media_buy.creative_approval_mode == auto_approve. A conformant inline-only auto-approve seller matches that gate legitimately, but thesupply_creativesphase invokessync_creativeswith no inline fallback.Its inline-only counterpart
inline_creatives_without_sync(gated onmedia_buy.features.inline_creative_management == true) passes against this same agent, so the vocabulary to express this correctly already exists — this scenario just doesn't use it.Suggested fixes
Scenario authoring — add an explicit
creative.has_creative_librarygate to every storyboard whose steps callsync_creatives:creative_fate_after_cancellation,dependency_impairment,dependency_impairment_cardinality:dependency_impairmentstoryboards this needs to compose with the existingpropagation_surfacesgate)pending_creatives_to_start: add the same gate, or branchsupply_creativesto the inline-creative path wheninline_creative_management == trueandhas_creative_library == false, mirroringinline_creatives_without_sync.Runner — two behavioral fixes, both of which change fail-open to fail-closed:
requires_capabilitypath. Absent should mean absent. If default-materialization is needed for some gates, make it opt-in per predicate (e.g.use_schema_default: true) rather than the global fallback.required_toolsto.every(), or add a genuinely conjunctive field so a storyboard can state that it needs a specific tool. Also worth reconciling the discovered-tool set with whattools/listactually returns (Fix Fly.io deployment timeout issues #2).Since both the 3.1.2 and 3.1.5 packs are affected, a scenario-side fix likely wants backporting across the 3.1.x cache line rather than landing only on tip.
Impact
Any spec-conformant inline-only seller —
has_creative_library: falseplusinline_creative_management: true, precisely the configurationinline_creatives_without_syncexists to validate — cannot obtain a cleanmedia_buy_sellerrun on any 3.1.x pack. Four scenarios reportfailwhere they should reportskip, and 26 further steps cascade toprerequisite_failed.Because
requires_capabilitycurrently fails open on any defaulted field, the blast radius is wider than these four storyboards: every gate whose target path carries a JSON Schema default is affected the same way.