Skip to content

feat(test-controller): add seed_creative_format scenario; advertise force_session_status in capabilities#315

Merged
bokelley merged 3 commits intomainfrom
claude/issue-314-seed-creative-format-controller-detection
Apr 30, 2026
Merged

feat(test-controller): add seed_creative_format scenario; advertise force_session_status in capabilities#315
bokelley merged 3 commits intomainfrom
claude/issue-314-seed-creative-format-controller-detection

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented Apr 30, 2026

Refs #314

Summary

Investigation of #314 found two Python SDK gaps that together explain the controller_detected: false problem (or are latent bugs that will surface imminently):

  1. seed_creative_format was missing from the Python SDK entirely. The comply-test-controller-request.json schema (AdCP 3.0.1) includes this scenario but it was absent from SCENARIOS, TestControllerStore, the dispatcher, and DemoStore. Any storyboard that seeds a creative format would receive UNKNOWN_SCENARIO.

  2. force_session_status was not advertised in DemoSeller's static compliance_testing.scenarios field. The get-adcp-capabilities-response.json schema allows exactly 6 scenario values in the static field; DemoSeller was only advertising 5 (missing force_session_status). The JS storyboard runner likely gates controller_detected on a check of this static field — it would not see the full set and flip the flag. Adding force_session_status to the static list and providing a minimal DemoStore stub brings the static and dynamic (list_scenarios) surfaces into alignment for this scenario.

What this PR does not fix: The newer scenarios (force_create_media_buy_arm, force_task_completion, seed_*) cannot be advertised in the static capabilities field until the get-adcp-capabilities-response.json schema enum is extended. That is a cross-repo fix against the AdCP spec; a follow-up issue should be filed against adcontextprotocol/adcp requesting the enum update.

Changes

  • src/adcp/server/test_controller.py: adds "seed_creative_format" to SCENARIOS, adds TestControllerStore.seed_creative_format() base method, adds dispatcher branch
  • examples/seller_agent.py: adds "force_session_status" to DemoSeller static capabilities, adds DemoStore.force_session_status() stub, adds DemoStore.seed_creative_format() implementation backed by seeded_creative_formats module dict, merges seeded formats into list_creative_formats response
  • tests/test_server_dx.py: three new tests — test_seed_creative_format_dispatches, test_seed_creative_format_in_list_scenarios, test_seed_creative_format_structured_fixture_id

What was tested

  • pytest tests/test_server_dx.py tests/test_test_controller_context.py -v → 58 passed, 0 failed
  • ruff check src/adcp/server/test_controller.py → clean
  • mypy src/adcp/server/test_controller.py → no issues

Pre-PR review

Pre-PR review:

  • code-reviewer: approved — found 1 blocker (fixed before PR open: data.get("format_id") could return a dict from a structured fixture, making it an unhashable dict key; fixed to (data.get("format_id") or {}).get("id")). Also noted test coverage gap for the structured-fixture path (added as third test in final commit).
  • ad-tech-protocol-expert: approved — force_session_status is schema-legal for a media_buy-only seller (in the 6-value capabilities enum). seed_creative_format correctly mirrors the pattern of other seed_* methods. Confirmed the blocker (dict-as-key) was already fixed. No additional blockers.

Nits (not fixed, noted for reviewer):

  • DemoStore.force_session_status always returns previous_state: "active". Acceptable for a demo seller that has no SI session state; multi-step storyboards that issue two force_session_status calls in sequence will see a misleading value on the second call. Production stores should track actual session state.
  • seeded_creative_formats is module-level state that is never cleared between compliance sessions (consistent with how all other module-level dicts in seller_agent.py behave, but worth noting since it influences what the protocol advertises as available formats).
  • list_creative_formats concatenates without deduplication. A seeded format_id colliding with a static format (display_300x250, display_970x250) would appear twice in the unfiltered response. Unlikely in practice given storyboard seed IDs.
  • TestControllerStore.seed_creative_format docstring notes the seller MUST wire the seeded ID into list_creative_formats but doesn't call this out as a subclass responsibility — future implementors could override without wiring the list method.

Triage-managed PR. This bot does not currently iterate on
review comments or PR conversation threads (only on the source
issue). To unblock:

  • Push fixup commits directly: gh pr checkout <num>
    fix → push.
  • Or re-trigger: comment /triage execute on the source
    issue.

See adcp#3121
for context.

Session: https://claude.ai/code/session_018i5iWUxUayQEh5K3v9HZRG

claude added 3 commits April 30, 2026 09:24
…orce_session_status

Closes #314

Two changes to close the gap revealed by issue investigation:

1. **seed_creative_format** — the comply-test-controller-request.json schema
   (AdCP 3.0.1) includes this scenario but it was missing from SCENARIOS,
   TestControllerStore, the _handle_test_controller dispatcher, and DemoStore.
   Any storyboard that seeds a creative format would receive UNKNOWN_SCENARIO.
   DemoStore.seed_creative_format() registers the format in a module-level
   seeded_creative_formats dict that list_creative_formats now merges in.

2. **force_session_status** — advertised in DemoSeller's static
   compliance_testing.scenarios (schema-legal: it's in the 3.0.1 capabilities
   enum) and given a minimal stub in DemoStore. This makes list_scenarios return
   all six schema-permitted scenarios, which is the most likely predicate the JS
   storyboard runner checks for controller_detected. A cross-repo fix is still
   needed to extend the capabilities schema enum to cover force_create_media_buy_arm,
   force_task_completion, and seed_* — those cannot be advertised in the static
   field until the schema is updated.

https://claude.ai/code/session_018i5iWUxUayQEh5K3v9HZRG
…eative_format

If the storyboard sends a fixture that contains format_id as a structured
object {"agent_url": ..., "id": ...}, data.get("format_id") returns a dict.
Using that dict as a seeded_creative_formats key raises TypeError (unhashable).
Extract the scalar id via .get("id") to stay consistent with how other seed_*
methods handle their fixture fields.

https://claude.ai/code/session_018i5iWUxUayQEh5K3v9HZRG
…d_creative_format

Pre-PR protocol review noted that neither new test exercised the case where
format_id arrives only inside fixture as a structured object rather than as a
top-level params.format_id string. Adds a test that passes the fixture path
and asserts the dispatcher correctly passes format_id=None to the store
(leaving ID extraction to the store's own logic).

https://claude.ai/code/session_018i5iWUxUayQEh5K3v9HZRG
@bokelley bokelley marked this pull request as ready for review April 30, 2026 13:25
@bokelley bokelley force-pushed the claude/issue-314-seed-creative-format-controller-detection branch from a858324 to 9c2afa9 Compare April 30, 2026 13:25
@bokelley bokelley merged commit ce7e9ab into main Apr 30, 2026
10 of 11 checks passed
@bokelley bokelley deleted the claude/issue-314-seed-creative-format-controller-detection branch April 30, 2026 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants