diff --git a/.changeset/proposal-mode-storyboard-fixture-fixes.md b/.changeset/proposal-mode-storyboard-fixture-fixes.md new file mode 100644 index 0000000000..d285836abe --- /dev/null +++ b/.changeset/proposal-mode-storyboard-fixture-fixes.md @@ -0,0 +1,17 @@ +--- +"adcontextprotocol": patch +--- + +compliance(storyboard): fix proposal-mode fixture authoring in `sales_proposal_mode` and `media_buy_seller/proposal_finalize` + +Two pre-existing storyboard authoring issues, surfaced by `@adcp/sdk` PR #1603 (which made `create_media_buy` actually exercise proposal-mode end-to-end instead of silently sending `packages` regardless): + +1. **`sales_proposal_mode`** authored `proposal_id: "balanced_reach_q2"` as a literal in two places (refine step + create_media_buy step). The training-agent's seed proposals don't include that id (it seeds `pinnacle_cross_channel`, `viewpoint_multi_screen`, `sparq_social_amplification`, `novamind_ai_audience`). Switched both to `$context.proposal_id` so the storyboard dynamically references whichever proposal the brief returned, matching the pattern `media_buy_seller/proposal_finalize` already uses. + +2. **Both storyboards** now include `io_acceptance` on the `create_media_buy` fixture. AdCP 3.0+ proposals with guaranteed inventory carry an `insertion_order` with `requires_signature: true` after finalization; sellers reject `create_media_buy` against such proposals without `io_acceptance`. The finalize step's `context_outputs` captures `proposals[0].insertion_order.io_id`, and the create_media_buy step references it via `$context.io_id`. + +3. **`sales_proposal_mode`** previously jumped straight from refine to create_media_buy, which kept the proposal in `draft` status. Added a `finalize_proposal` phase between them (matching the pattern in `media_buy_seller/proposal_finalize`) so the proposal transitions to `committed` before acceptance. + +Forward-compatible with both pre-#1603 and post-#1603 SDK behavior — all six tenant matrix runs pass against both. /sales lifts from 258 → 259 steps (the new finalize step counts). + +Patch-eligible per the conformance-additive rule (additive scenarios / fixture corrections that bring storyboards into alignment with the spec's own normative proposal-lifecycle). diff --git a/static/compliance/source/protocols/media-buy/scenarios/proposal_finalize.yaml b/static/compliance/source/protocols/media-buy/scenarios/proposal_finalize.yaml index ac48c7549a..a1f5fd6333 100644 --- a/static/compliance/source/protocols/media-buy/scenarios/proposal_finalize.yaml +++ b/static/compliance/source/protocols/media-buy/scenarios/proposal_finalize.yaml @@ -199,6 +199,10 @@ phases: domain: "acmeoutdoor.example" operator: "pinnacle-agency.example" + context_outputs: + - path: "proposals[0].insertion_order.io_id" + key: "io_id" + validations: - check: response_schema description: "Response matches get-products-response.json schema" @@ -244,6 +248,10 @@ phases: currency: "USD" start_time: "2026-04-01T00:00:00Z" end_time: "2026-06-30T23:59:59Z" + io_acceptance: + io_id: "$context.io_id" + accepted_at: "2026-03-15T14:30:00Z" + signatory: "ops@acmeoutdoor.example" idempotency_key: "$generate:uuid_v4#media_buy_seller_proposal_finalize_accept_proposal_create_media_buy" validations: diff --git a/static/compliance/source/specialisms/sales-proposal-mode/index.yaml b/static/compliance/source/specialisms/sales-proposal-mode/index.yaml index 4bc900b435..a6f5acbd6a 100644 --- a/static/compliance/source/specialisms/sales-proposal-mode/index.yaml +++ b/static/compliance/source/specialisms/sales-proposal-mode/index.yaml @@ -259,7 +259,7 @@ phases: buying_mode: "refine" refine: - scope: "proposal" - proposal_id: "balanced_reach_q2" + proposal_id: "$context.proposal_id" ask: "Shift 60% of budget to CTV. Drop the display product and redistribute that budget to video." - scope: "request" ask: "All products must support frequency capping at 3 per day." @@ -293,6 +293,58 @@ phases: - check: field_present path: "products[0].format_ids[0].id" description: "Format IDs include id — must be accepted back in sync_creatives" + - id: finalize_proposal + title: "Finalize the proposal" + narrative: | + Before accepting, the buyer requests finalization. This transitions the proposal from + draft (indicative pricing) to committed (firm pricing with inventory hold). For + guaranteed inventory, finalization also produces an insertion order the buyer + countersigns when calling create_media_buy. + + steps: + - id: get_products_finalize + title: "Finalize the refined proposal" + narrative: | + The buyer requests finalization on the refined proposal. The seller commits + firm pricing and (for guaranteed inventory) returns an insertion order with + a signature requirement. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: true + expected: | + Return the finalized proposal with committed status: + - proposals[0].proposal_status: committed + - proposals[0].expires_at: timestamp for the inventory hold window + - For guaranteed inventory, proposals[0].insertion_order with io_id + - The proposal is ready to accept via create_media_buy + + sample_request: + buying_mode: "refine" + refine: + - scope: "proposal" + proposal_id: "$context.proposal_id" + action: "finalize" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "sales_proposal_mode--get_products_finalize" + + context_outputs: + - path: "proposals[0].insertion_order.io_id" + key: "io_id" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "proposals" + description: "Response contains the finalized proposal" - id: accept_proposal title: "Accept the proposal" narrative: | @@ -334,12 +386,16 @@ phases: brand: domain: "acmeoutdoor.example" operator: "pinnacle-agency.example" - proposal_id: "balanced_reach_q2" + proposal_id: "$context.proposal_id" total_budget: amount: 50000 currency: "USD" start_time: "2026-04-01T00:00:00Z" end_time: "2026-06-30T23:59:59Z" + io_acceptance: + io_id: "$context.io_id" + accepted_at: "2026-03-15T14:30:00Z" + signatory: "ops@acmeoutdoor.example" idempotency_key: "$generate:uuid_v4#sales_proposal_mode_accept_proposal_create_media_buy" context: