id: media_buy_seller/completed_views_buy_flow version: "1.0.0" title: "Seller fulfills a video media buy with a completed_views optimization goal (target CPCV)" category: media_buy_seller summary: "Verifies that a seller advertising completed_views in supported_optimization_metrics can accept a media buy whose metric-kind optimization_goal targets completed views at a buyer-supplied view_duration_seconds, reject view_duration_seconds values not in the product's metric_optimization.supported_view_durations, and report completed_views + completion_rate on delivery. Sibling to reach_buy_flow on the video-completion side: sellers without video inventory (display-only DSPs, retail-media networks, audio-only sellers without video products) grade not_applicable." track: media_buy # Gate: scenario runs only when the seller declares completed_views in # media_buy.supported_optimization_metrics (proposed in #4669). Sellers # without video inventory or whose video products don't optimize for # completion (display-only DSPs, retail-media networks, audio-only # sellers without video) grade `not_applicable`, not failing. Gating # on the explicit metric in supported_optimization_metrics avoids # over-claiming against the broad delivery-tracking community. # Requires runner support for the `contains:` matcher (adcp-client >= 7.70). requires_capability: path: media_buy.supported_optimization_metrics contains: "completed_views" required_tools: - get_products - create_media_buy - get_media_buy_delivery - comply_test_controller invariants: - status.monotonic narrative: | A "completed-views buy" (CPCV — cost per completed view) is a video or audio media buy whose optimization_goal is metric-kind with metric: completed_views, a buyer-supplied view_duration_seconds (e.g., 2 for Snap/LinkedIn defaults, 6 for TikTok, 15 for Meta ThruPlay), and a target.kind: cost_per target. The view_duration_seconds value MUST be in the product's metric_optimization.supported_view_durations — silent rounding to the seller's nearest supported duration would create measurement discrepancies (a buyer targeting 6-second completion would unknowingly be billed against 2-second or 15-second completion). Discriminating behaviors this scenario verifies: 1. create_media_buy with metric: completed_views, a view_duration_seconds value declared in the product's supported_view_durations, and a target.kind: cost_per target is accepted. 2. create_media_buy with a view_duration_seconds value NOT in the product's supported_view_durations is rejected with INVALID_REQUEST. error.field points at the offending view_duration_seconds path. optimization-goal.json:50-53 makes this requirement explicit: "Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies." 3. get_media_buy_delivery surfaces totals.completed_views and totals.completion_rate on delivery. completion_rate is defined in delivery-metrics.json as completed_views / impressions; sellers optimizing for completed_views MUST populate both fields. The 999-second view_duration is chosen for the rejection arm because no honest video product will declare a 999-second duration in supported_view_durations — common values are 2, 6, 15, and 30. A seller that round-trips 999 as accepted is demonstrating they don't validate against their product capability declarations. agent: interaction_model: media_buy_seller capabilities: - sells_media - supports_non_guaranteed examples: - "CTV sellers optimizing for video completion" - "Social platforms with video completion goals (Meta ThruPlay, TikTok 6-second views)" - "Audio sellers optimizing for podcast/stream completion" - "Online video DSPs with CPCV pricing" caller: role: buyer_agent example: "Pinnacle Agency (buyer)" prerequisites: description: | The seller must implement comply_test_controller with simulate_delivery (already required for delivery_reporting). simulate_delivery injects impressions and completed_views so get_media_buy_delivery has something to surface for the completion-optimized buy. The buyer fixture supplies a brand, an operator, and video-channel targeting parameters sufficient to exercise the view_duration_seconds binding contract and the unsupported-value rejection. test_kit: "test-kits/acme-outdoor.yaml" controller_seeding: true fixtures: products: - product_id: "cpcv_video_q2" delivery_type: "non_guaranteed" channels: ["video"] format_ids: - id: "video_30s" pricing_options: - product_id: "cpcv_video_q2" pricing_option_id: "auction_cpm" pricing_model: "cpm" currency: "USD" floor_price: 12.00 phases: - id: setup title: "Establish account and discover products" steps: - id: sync_accounts title: "Establish account" task: sync_accounts schema_ref: "account/sync-accounts-request.json" response_schema_ref: "account/sync-accounts-response.json" doc_ref: "/accounts/tasks/sync_accounts" stateful: true expected: | Return the account with account_id and status active. sample_request: accounts: - brand: domain: "acmeoutdoor.example" operator: "pinnacle-agency.example" billing: "operator" idempotency_key: "$generate:uuid_v4#completed_views_buy_flow_setup_sync_accounts" validations: - check: response_schema description: "Response matches sync-accounts-response.json schema" - check: field_present path: "accounts[0].account_id" description: "Account has a platform-assigned ID" - id: get_products_for_cpcv title: "Discover video products that support completed_views optimization" 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" stateful: false expected: | Return at least one video product whose metric_optimization capabilities include completed_views as an optimization metric and declare supported_view_durations. The buyer selects a duration from that list for the create_media_buy call below. sample_request: buying_mode: "brief" brief: "Online video / CTV, US adults 25-54. Q2 flight ~$30K, optimizing for completed views at a 6-second threshold with target CPCV." account: brand: domain: "acmeoutdoor.example" operator: "pinnacle-agency.example" validations: - check: response_schema description: "Response matches get-products-response.json schema" - check: field_present path: "products" description: "Response contains products" - id: create_cpcv_buy title: "Create a completed-views buy at a supported view_duration" narrative: | The buyer creates a media buy whose package carries a metric-kind optimization_goal with metric: completed_views, view_duration_seconds: 6 (a common supported value for online video — TikTok native, Meta 6-second qualifier), and target.kind: cost_per at $0.05 per completed view. The seller should accept and return a media_buy_id used for subsequent delivery checks. steps: - id: create_media_buy_cpcv title: "Create media buy with metric-kind completed_views goal" task: create_media_buy schema_ref: "media-buy/create-media-buy-request.json" response_schema_ref: "media-buy/create-media-buy-response.json" doc_ref: "/media-buy/task-reference/create_media_buy" stateful: true expected: | Create the media buy. Response carries a media_buy_id used for subsequent get_media_buy_delivery calls. sample_request: brand: domain: "acmeoutdoor.example" account: brand: domain: "acmeoutdoor.example" operator: "pinnacle-agency.example" start_time: "asap" end_time: "2099-06-30T23:59:59Z" packages: - product_id: "cpcv_video_q2" budget: 30000 pricing_option_id: "auction_cpm" optimization_goals: - kind: "metric" metric: "completed_views" view_duration_seconds: 6 target: kind: "cost_per" value: 0.05 priority: 1 idempotency_key: "$generate:uuid_v4#completed_views_buy_flow_create_cpcv_buy_create_media_buy" context_outputs: - name: media_buy_id path: "media_buy_id" validations: - check: response_schema description: "Response matches create-media-buy-response.json schema" - check: field_present path: "media_buy_id" description: "Media buy has a platform-assigned ID" - id: rejection_unsupported_view_duration title: "Reject a completed_views goal whose view_duration_seconds is not in supported_view_durations" narrative: | Silent acceptance of a view_duration_seconds value not declared in the product's metric_optimization.supported_view_durations is a façade — the seller either rounds to the nearest supported duration (creating measurement discrepancies where the buyer thinks they're paying for 999-second completion but is billed against the rounded duration) or runs without a duration (delivery becomes uncomparable). The seller MUST reject with INVALID_REQUEST and set error.field to the offending view_duration_seconds path. Same shape as the unsupported reach_unit rejection in reach_buy_flow. optimization-goal.json:50-53 makes this requirement explicit: "Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies." The literal 999 is chosen because no honest video product will declare a 999-second duration — common values are 2, 6, 15, and 30. steps: - id: create_media_buy_with_phantom_view_duration title: "Submit a completed_views goal whose view_duration_seconds is not in supported_view_durations" task: create_media_buy schema_ref: "media-buy/create-media-buy-request.json" response_schema_ref: "media-buy/create-media-buy-response.json" doc_ref: "/media-buy/task-reference/create_media_buy" expect_error: true negative_path: payload_well_formed stateful: false expected: | Reject with INVALID_REQUEST. error.field points at the offending view_duration_seconds path. media_buy_id is not allocated. sample_request: brand: domain: "acmeoutdoor.example" account: brand: domain: "acmeoutdoor.example" operator: "pinnacle-agency.example" start_time: "asap" end_time: "2099-06-30T23:59:59Z" packages: - product_id: "cpcv_video_q2" budget: 5000 pricing_option_id: "auction_cpm" optimization_goals: - kind: "metric" metric: "completed_views" view_duration_seconds: 999 target: kind: "cost_per" value: 0.05 idempotency_key: "$generate:uuid_v4#completed_views_buy_flow_rejection_unsupported_view_duration" validations: - check: error_code allowed_values: ["INVALID_REQUEST"] description: "Seller rejects the unsupported view_duration_seconds with INVALID_REQUEST" - check: field_value path: "errors[0].field" value: "packages[0].optimization_goals[0].view_duration_seconds" description: "Error.field points at the offending view_duration_seconds path (core/error.json defines field as deterministic JSONPath-lite — literal equality)" - id: cpcv_delivery title: "Delivery reporting carries completed_views and completion_rate" narrative: | The discriminating assertion of the completed-views buy mode: delivery reporting MUST surface completed_views and completion_rate — not just impressions and spend. The runner injects simulated impressions and completed_views via the test controller, then verifies get_media_buy_delivery returns completed_views + completion_rate on totals. completion_rate is defined in delivery-metrics.json as completed_views / impressions; sellers optimizing for completed_views at a target view_duration_seconds are committing to populate both fields on delivery responses. steps: - id: simulate_cpcv_delivery title: "Inject impressions + completed_views + spend" task: comply_test_controller requires_tool: comply_test_controller stateful: true expected: | The test controller acknowledges the simulated delivery. sample_request: account: sandbox: true scenario: "simulate_delivery" params: media_buy_id: "$context.media_buy_id" impressions: 500000 completed_views: 350000 reported_spend: amount: 18000.00 currency: "USD" validations: - check: field_value path: "success" allowed_values: [true] description: "Delivery simulation succeeds" - id: get_cpcv_delivery title: "Get delivery and verify completed_views + completion_rate metrics" task: get_media_buy_delivery schema_ref: "media-buy/get-media-buy-delivery-request.json" response_schema_ref: "media-buy/get-media-buy-delivery-response.json" doc_ref: "/media-buy/task-reference/get_media_buy_delivery" stateful: true expected: | Delivery response includes totals.completed_views (the completion count at the declared view_duration_seconds threshold) and totals.completion_rate (completed_views / impressions). These two fields are the discriminating output of completed-views optimization and are required on completion-optimized buys. sample_request: account: brand: domain: "acmeoutdoor.example" operator: "pinnacle-agency.example" media_buy_ids: - "$context.media_buy_id" include_package_daily_breakdown: true validations: - check: response_schema description: "Response matches get-media-buy-delivery-response.json schema" - check: field_present path: "media_buy_deliveries[0].totals.completed_views" description: "Completed-views count surfaced at the buy level (at the declared view_duration_seconds threshold)" - check: field_present path: "media_buy_deliveries[0].totals.completion_rate" description: "Completion rate surfaced at the buy level (completed_views / impressions)"