Context
PR #282 just landed the SDK base-class methods for `force_create_media_buy_arm` + `force_task_completion`, and PR #296 landed stubs for the 5 `seed_*` scenarios (AdCP 3.0.1). The SDK's `_list_scenarios` helper introspects which methods the store overrides — only overridden ones get advertised in `list_scenarios` and only those count toward the JS storyboard runner's controller-detection probe.
`DemoStore` in `examples/seller_agent.py` overrides 5 of the older scenarios (`force_creative_status`, `force_account_status`, `force_media_buy_status`, `simulate_delivery`, `simulate_budget_spend`) but none of the new ones. Result: `controller_detected: false` in the storyboard runner, and 5 step failures cascade because storyboards depending on seeded fixtures (`outdoor_display_q2` product, `acme_outdoor_allowlist_v1` property list) can't run.
Current storyboard score: 36/47 passing, 5 failed, 6 skipped, `controller_detected: false`.
Proposal
Override the following in `DemoStore`, mutating the example's in-memory `PRODUCTS` / `media_buys` / `creatives` dicts:
Required for `controller_detected: true`
-
`force_create_media_buy_arm(arm, task_id, message, account)` — store a single-shot directive on the seller (e.g. `pending_directives[account_id] = {"arm": arm, "task_id": task_id, "message": message}`); the next `create_media_buy` from the same account checks for and consumes the directive.
-
`force_task_completion(task_id, result, account)` — record a completion payload to deliver to the buyer's `push_notification_config.url` on the next polling tick (or synchronously if the example doesn't model webhooks).
Required to unblock the 5 fixture-dependent storyboards
-
`seed_product(fixture, product_id)` — append/replace in the example's `PRODUCTS` list. Closes the `Product 'sports_preroll_q2' not found` and `Product 'outdoor_display_q2' not found` failures.
-
`seed_media_buy(fixture, media_buy_id)` — pre-populate the example's `media_buys` dict with `targeting_overlay` populated. Closes the `acme_outdoor_allowlist_v1` / `acme_outdoor_no_match_v1` validation failures in the `inventory_list_targeting` storyboard.
Optional (nice-to-have for completeness)
- `seed_pricing_option`, `seed_creative`, `seed_plan` — same pattern, append to the appropriate dict.
Verification
Once these overrides land, re-run:
```
ADCP_PORT=3001 python examples/seller_agent.py &
sleep 2
npx -y -p @adcp/client@latest adcp storyboard run \
http://127.0.0.1:3001/mcp media_buy_seller --json --allow-http \
| jq '{overall_status, controller_detected, summary}'
```
Target: `overall_status: pass`, `controller_detected: true`, `summary.steps_passed: 47`. The storyboard CI job from #305 (currently `continue-on-error: true`) can be promoted to required.
References
Context
PR #282 just landed the SDK base-class methods for `force_create_media_buy_arm` + `force_task_completion`, and PR #296 landed stubs for the 5 `seed_*` scenarios (AdCP 3.0.1). The SDK's `_list_scenarios` helper introspects which methods the store overrides — only overridden ones get advertised in `list_scenarios` and only those count toward the JS storyboard runner's controller-detection probe.
`DemoStore` in `examples/seller_agent.py` overrides 5 of the older scenarios (`force_creative_status`, `force_account_status`, `force_media_buy_status`, `simulate_delivery`, `simulate_budget_spend`) but none of the new ones. Result: `controller_detected: false` in the storyboard runner, and 5 step failures cascade because storyboards depending on seeded fixtures (`outdoor_display_q2` product, `acme_outdoor_allowlist_v1` property list) can't run.
Current storyboard score: 36/47 passing, 5 failed, 6 skipped, `controller_detected: false`.
Proposal
Override the following in `DemoStore`, mutating the example's in-memory `PRODUCTS` / `media_buys` / `creatives` dicts:
Required for `controller_detected: true`
`force_create_media_buy_arm(arm, task_id, message, account)` — store a single-shot directive on the seller (e.g. `pending_directives[account_id] = {"arm": arm, "task_id": task_id, "message": message}`); the next `create_media_buy` from the same account checks for and consumes the directive.
`force_task_completion(task_id, result, account)` — record a completion payload to deliver to the buyer's `push_notification_config.url` on the next polling tick (or synchronously if the example doesn't model webhooks).
Required to unblock the 5 fixture-dependent storyboards
`seed_product(fixture, product_id)` — append/replace in the example's `PRODUCTS` list. Closes the `Product 'sports_preroll_q2' not found` and `Product 'outdoor_display_q2' not found` failures.
`seed_media_buy(fixture, media_buy_id)` — pre-populate the example's `media_buys` dict with `targeting_overlay` populated. Closes the `acme_outdoor_allowlist_v1` / `acme_outdoor_no_match_v1` validation failures in the `inventory_list_targeting` storyboard.
Optional (nice-to-have for completeness)
Verification
Once these overrides land, re-run:
```
ADCP_PORT=3001 python examples/seller_agent.py &
sleep 2
npx -y -p @adcp/client@latest adcp storyboard run \
http://127.0.0.1:3001/mcp media_buy_seller --json --allow-http \
| jq '{overall_status, controller_detected, summary}'
```
Target: `overall_status: pass`, `controller_detected: true`, `summary.steps_passed: 47`. The storyboard CI job from #305 (currently `continue-on-error: true`) can be promoted to required.
References