Skip to content

Implement force_create_media_buy_arm + force_task_completion controller scenarios for AdCP storyboard parity #97

@bokelley

Description

@bokelley

Context

The AdCP spec recently added two compliance-controller scenarios for testing the async create_media_buy lifecycle deterministically:

  • force_create_media_buy_arm (adcp#3104) — registers a single-shot directive that drives the next create_media_buy call into a specific arm (submitted, input-required) with a buyer-supplied task_id. Sellers MUST honor the directive verbatim on the next call from the same authenticated sandbox account.
  • force_task_completion (adcp#3138) — resolves a previously-submitted task to completed with a buyer-supplied result payload (validated against async-response-data.json). Cross-account replays return NOT_FOUND; identical-params replays are idempotent; diverging-params replays against a terminal task return INVALID_TRANSITION.

The training-agent in the adcp repo implements both (#3115, #3194). For storyboard-runner conformance against adcp-go to grade passing rather than not_applicable, the Go reference seller needs parity implementations.

Scope

Add the two scenarios to whatever this repo's compliance-controller surface looks like (mirror the structure of the existing force_*_status and simulate_* scenarios):

  1. force_create_media_buy_arm with params { arm: 'submitted' | 'input-required', task_id?: string, message?: string }. Validate task_id is required when arm = submitted, max 128 chars; message max 2000 chars. Single-shot per (account, principal): consumed by the next create_media_buy, then cleared. A second registration before consumption overwrites. Returns ForcedDirectiveSuccess shape.
  2. force_task_completion with params { task_id: string, result: object }. Validate task_id ≤128 chars, result is a non-empty object (256 KB soft cap). Records (task_id, result, ownerKey). Cross-account replays → NOT_FOUND. Identical-params replays → idempotent. Diverging-params replays against terminal → INVALID_TRANSITION. Returns StateTransitionSuccess with previous_state: 'submitted' / current_state: 'completed'.
  3. Advertise both in list_scenarios so storyboard runners detect support.

Reference implementations (mechanical port):

  • adcp/server/src/training-agent/comply-test-controller.ts (force_create_media_buy_arm + force_task_completion handlers, lines ~470-700)
  • adcp/server/tests/unit/training-agent-force-create-media-buy-arm.test.ts (test patterns)
  • adcp/server/tests/unit/training-agent-force-task-completion.test.ts (test patterns)

Acceptance

  • Sellers built on adcp-go running the AdCP storyboard suite hit the create_media_buy_async.yaml scenario and grade passing on the submitted-arm phase.
  • list_scenarios advertises both new entries.
  • Test coverage at parity with the training-agent's nine-test pattern: registration with valid params, INVALID_PARAMS branches, replay idempotency, diverging-replay INVALID_TRANSITION, cross-account isolation, list_scenarios advertisement.

Caveats

  • Buyer-side polling integration (the tasks/get round-trip) is deferred until two upstream gaps in @adcp/client (Node) are resolved: caller-supplied IDs in non-Postgres task stores and AdCP-shaped tasks/get response handler. Tracked in adcp-client#994. This PR's scope is the controller-side primitive only.

Related

  • adcp#3104 — force_create_media_buy_arm spec
  • adcp#3138 — force_task_completion spec
  • adcp#3115, adcp#3194 — Node training-agent implementations to mirror
  • adcp-client#994 — umbrella issue for the upstream gaps

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions