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-client-python to grade passing rather than not_applicable, the Python 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):
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.
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'.
- 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-client-python 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: InMemoryTaskStore parity for caller-supplied IDs and AdCP-shaped tasks/get response handler. Tracked in adcp-client#994. This PR's scope is the controller-side primitive only.
- Whether this repo's task store has caller-supplied-id support is its own design question. The training-agent uses a process-global Map (no SDK task-store integration) for the same reasons.
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
Context
The AdCP spec recently added two compliance-controller scenarios for testing the async
create_media_buylifecycle deterministically:force_create_media_buy_arm(adcp#3104) — registers a single-shot directive that drives the nextcreate_media_buycall into a specific arm (submitted,input-required) with a buyer-suppliedtask_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 tocompletedwith a buyer-suppliedresultpayload (validated againstasync-response-data.json). Cross-account replays returnNOT_FOUND; identical-params replays are idempotent; diverging-params replays against a terminal task returnINVALID_TRANSITION.The training-agent in the
adcprepo implements both (#3115, #3194). For storyboard-runner conformance againstadcp-client-pythonto gradepassingrather thannot_applicable, the Python 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_*_statusandsimulate_*scenarios):force_create_media_buy_armwith params{ arm: 'submitted' | 'input-required', task_id?: string, message?: string }. Validatetask_idis required when arm = submitted, max 128 chars;messagemax 2000 chars. Single-shot per (account, principal): consumed by the nextcreate_media_buy, then cleared. A second registration before consumption overwrites. ReturnsForcedDirectiveSuccessshape.force_task_completionwith params{ task_id: string, result: object }. Validatetask_id≤128 chars,resultis 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. ReturnsStateTransitionSuccesswithprevious_state: 'submitted'/current_state: 'completed'.list_scenariosso storyboard runners detect support.Reference implementations (mechanical port):
Acceptance
adcp-client-pythonrunning the AdCP storyboard suite hit thecreate_media_buy_async.yamlscenario and gradepassingon the submitted-arm phase.list_scenariosadvertises both new entries.Caveats
tasks/getround-trip) is deferred until two upstream gaps in@adcp/client(Node) are resolved:InMemoryTaskStoreparity for caller-supplied IDs and AdCP-shapedtasks/getresponse handler. Tracked in adcp-client#994. This PR's scope is the controller-side primitive only.Related
force_create_media_buy_armspecforce_task_completionspec