From 7a5fe61b3a599e8390bc7ae451db898b34ee1292 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 20:59:51 +0000 Subject: [PATCH] feat(compliance): add vendor_metric optimization-goal storyboard coverage (#4933) Adds storyboard media_buy_seller/vendor_metric_optimization_flow exercising the 3.1 vendor_metric goal contract: positive acceptance when all preconditions are met, plus two negative paths for capability mismatch and reporting-coherence failure. Updates training-agent publishers.ts + product-factory.ts to surface vendor_metric_optimization on products. https://claude.ai/code/session_01VtKu8sAUEWAHRPMiZJBgHw --- .../vendor-metric-optimization-storyboard.md | 4 + server/src/training-agent/product-factory.ts | 1 + server/src/training-agent/publishers.ts | 10 + server/src/training-agent/types.ts | 8 + .../vendor_metric_optimization_flow.yaml | 336 ++++++++++++++++++ 5 files changed, 359 insertions(+) create mode 100644 .changeset/vendor-metric-optimization-storyboard.md create mode 100644 static/compliance/source/protocols/media-buy/scenarios/vendor_metric_optimization_flow.yaml diff --git a/.changeset/vendor-metric-optimization-storyboard.md b/.changeset/vendor-metric-optimization-storyboard.md new file mode 100644 index 0000000000..c37862d24f --- /dev/null +++ b/.changeset/vendor-metric-optimization-storyboard.md @@ -0,0 +1,4 @@ +--- +--- + +Add vendor_metric optimization-goal storyboard coverage (issue #4933). New storyboard exercises the 3.1 vendor_metric goal contract: positive acceptance when all preconditions are met, and two negative paths (capability mismatch, reporting-coherence mismatch). Training-agent publishers.ts and product-factory.ts updated to surface vendor_metric_optimization on products. diff --git a/server/src/training-agent/product-factory.ts b/server/src/training-agent/product-factory.ts index 425ddccd0b..35e50126e7 100644 --- a/server/src/training-agent/product-factory.ts +++ b/server/src/training-agent/product-factory.ts @@ -562,6 +562,7 @@ function buildProduct( } as NonNullable, ...(pub.catalogTypes?.length && { catalog_types: pub.catalogTypes as CatalogType[] }), ...(metricOptimization && { metric_optimization: metricOptimization }), + ...(pub.vendorMetricOptimization && { vendor_metric_optimization: pub.vendorMetricOptimization }), ...(forecast && { forecast }), ...(conversionTracking && { conversion_tracking: conversionTracking }), ...(collectionSelectors && { collections: collectionSelectors }), diff --git a/server/src/training-agent/publishers.ts b/server/src/training-agent/publishers.ts index 1c91827797..dbc0d76af6 100644 --- a/server/src/training-agent/publishers.ts +++ b/server/src/training-agent/publishers.ts @@ -31,7 +31,17 @@ export const PUBLISHERS: PublisherProfile[] = [ reportingMetrics: ['impressions', 'spend', 'clicks', 'ctr', 'viewability', 'completed_views', 'completion_rate'], vendorMetrics: [ { vendor: { domain: 'attentionvendor.example' }, metric_id: 'attention_units' }, + { vendor: { domain: 'attentionvendor.example' }, metric_id: 'attention_score' }, ], + vendorMetricOptimization: { + supported_metrics: [ + { + vendor: { domain: 'attentionvendor.example' }, + metric_id: 'attention_score', + supported_targets: ['threshold_rate'], + }, + ], + }, properties: [ { propertyId: 'pinnacle_web', diff --git a/server/src/training-agent/types.ts b/server/src/training-agent/types.ts index eccff29271..e4f89fe91c 100644 --- a/server/src/training-agent/types.ts +++ b/server/src/training-agent/types.ts @@ -95,6 +95,14 @@ export interface PublisherProfile { vendor: { domain: string; brand_id?: string }; metric_id: string; }>; + /** Optional: vendor-attested metric optimization capabilities (3.1 vendor_metric goal kind) */ + vendorMetricOptimization?: { + supported_metrics: Array<{ + vendor: { domain: string; brand_id?: string }; + metric_id: string; + supported_targets?: Array<'cost_per' | 'threshold_rate'>; + }>; + }; /** Optional: shows this publisher carries */ shows?: ShowDefinition[]; /** Hero image URL for product and proposal cards */ diff --git a/static/compliance/source/protocols/media-buy/scenarios/vendor_metric_optimization_flow.yaml b/static/compliance/source/protocols/media-buy/scenarios/vendor_metric_optimization_flow.yaml new file mode 100644 index 0000000000..7c9523012f --- /dev/null +++ b/static/compliance/source/protocols/media-buy/scenarios/vendor_metric_optimization_flow.yaml @@ -0,0 +1,336 @@ +id: media_buy_seller/vendor_metric_optimization_flow +version: "1.0.0" +title: "Vendor-metric optimization-goal: acceptance and rejection" +category: media_buy_seller +summary: "Verifies that a seller supporting vendor_metric_optimization accepts a media buy whose optimization_goal has kind 'vendor_metric' when all preconditions are met (capability declared, committed_metrics coherent), and rejects goals that fail either the capability check or the reporting-coherence check." +track: media_buy +required_tools: + - get_products + - create_media_buy + - comply_test_controller + +narrative: | + AdCP 3.1 adds optimization_goals[].kind: "vendor_metric" — a goal that steers + the seller's bidding stack toward a specific vendor's measurement metric (e.g., + DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift). Three runtime + preconditions govern acceptance: + + 1. Capability — the (vendor, metric_id) pair MUST appear in the product's + vendor_metric_optimization.supported_metrics[], and the goal's target.kind + MUST appear in that entry's supported_targets. + + 2. Reporting coherence — the package's committed_metrics[] MUST include a + matching { scope: "vendor", vendor, metric_id } entry. Optimization without + committed reporting is unverifiable and is rejected at the wire level. + + 3. Discovery (SHOULD) — the metric_id SHOULD be discoverable in the vendor's + published measurement.metrics[] catalog. This check is SHOULD-level in 3.1 + while measurement-vendor adoption of AdCP-conformant capability publication + catches up; it tightens to MUST at the next minor. Sellers MAY reject with + INVALID_REQUEST on a catalog miss; sellers that accept do not fail this + storyboard. + + This storyboard covers: + - Positive path: goal accepted when all preconditions are met + - Negative path A: goal rejected when (vendor, metric_id) not in supported_metrics + - Negative path B: goal rejected when package committed_metrics lacks vendor-scope entry + + The existing vendor_metric_accountability storyboard (declaration → filter → + delivery emission) is unchanged and exercises a separate contract. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + examples: + - "Premium publishers with attention measurement optimization" + - "Sustainability-focused sellers with emissions-metric steering" + - "Sellers integrating brand-lift vendors into their bidding stack" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The seller supports comply_test_controller and can seed products with + vendor_metric_optimization.supported_metrics[]. The seeded product must + appear in get_products responses. The seller enforces the three preconditions + for vendor_metric goals at create_media_buy time. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "display_vendor_metric_opt" + delivery_type: "non_guaranteed" + channels: ["display"] + format_ids: + - id: "display_300x250" + reporting_capabilities: + available_metrics: + - impressions + - clicks + - spend + vendor_metrics: + - vendor: + domain: "attentionvendor.example" + metric_id: "attention_score" + vendor_metric_optimization: + supported_metrics: + - vendor: + domain: "attentionvendor.example" + metric_id: "attention_score" + supported_targets: + - threshold_rate + pricing_options: + - product_id: "display_vendor_metric_opt" + pricing_option_id: "cpm_vendor_opt" + pricing_model: "cpm" + currency: "USD" + fixed_price: 14.0 + +phases: + - id: discover_vendor_metric_optimization + title: "Discover a product that declares vendor_metric_optimization" + narrative: | + The buyer looks for display inventory that can steer delivery toward an + attention vendor's metric. The product response includes + vendor_metric_optimization.supported_metrics[] listing the attentionvendor.example + attention_score with threshold_rate as a supported target kind. + 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" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#media_buy_seller_vendor_metric_opt_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_vendor_metric_opt + title: "Discover product with vendor_metric_optimization capability" + 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: false + expected: | + Return at least one product whose vendor_metric_optimization.supported_metrics[] + includes an entry for attentionvendor.example / attention_score. The + supported_targets on that entry includes threshold_rate. + sample_request: + buying_mode: "brief" + brief: "Display inventory with attention-score optimization from attentionvendor." + filters: + channels: ["display"] + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context_outputs: + - path: "products[0].product_id" + name: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + name: "pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products[0].product_id" + description: "At least one product returned" + - check: field_present + path: "products[0].vendor_metric_optimization.supported_metrics" + description: "Product declares vendor_metric_optimization.supported_metrics[]" + + - id: create_positive_path + title: "Accept vendor_metric goal when all preconditions are satisfied" + narrative: | + The buyer submits a create_media_buy with an optimization_goal of kind + vendor_metric, targeting attention_score with a threshold_rate of 70. The + package also includes a vendor-scope committed_metrics[] entry for the same + (vendor, metric_id) pair — satisfying the reporting-coherence precondition. + The seller accepts and returns a media_buy_id. + steps: + - id: create_media_buy_positive + title: "Create media buy with valid vendor_metric optimization 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" + comply_scenario: create_media_buy + stateful: true + expected: | + Accept the buy. The optimization goal is valid: attention_score is in + supported_metrics for attentionvendor.example, threshold_rate is in + supported_targets, and committed_metrics[] carries the vendor-scope entry. + Seller returns a media_buy_id. + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + start_time: "2026-06-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "$context.product_id" + pricing_option_id: "$context.pricing_option_id" + budget: 5000 + optimization_goals: + - kind: "vendor_metric" + vendor: + domain: "attentionvendor.example" + metric_id: "attention_score" + target: + kind: "threshold_rate" + value: 70 + priority: 1 + committed_metrics: + - scope: "vendor" + vendor: + domain: "attentionvendor.example" + metric_id: "attention_score" + idempotency_key: "$generate:uuid_v4#media_buy_seller_vendor_metric_opt_positive" + context_outputs: + - path: "media_buy_id" + name: "media_buy_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Seller returns a media_buy_id — buy accepted" + + - id: reject_unsupported_capability + title: "Reject vendor_metric goal when (vendor, metric_id) not in supported_metrics" + narrative: | + The buyer sends a goal for emissions_score from attentionvendor.example — + a (vendor, metric_id) pair that does not appear in the product's + vendor_metric_optimization.supported_metrics[]. Per the spec, the seller + MUST reject such goals. The correct code is INVALID_REQUEST; error.field + points at the offending goal's vendor/metric_id path. + steps: + - id: create_media_buy_unsupported_metric + title: "Submit goal with (vendor, metric_id) not in supported_metrics" + 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" + comply_scenario: create_media_buy + expect_error: true + negative_path: payload_well_formed + stateful: false + expected: | + Reject with INVALID_REQUEST. The goal references emissions_score for + attentionvendor.example, which is not in supported_metrics — the capability + precondition fails. error.field should point at the optimization goal's + metric_id path. media_buy_id is not allocated. + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + start_time: "2026-06-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "$context.product_id" + pricing_option_id: "$context.pricing_option_id" + budget: 5000 + optimization_goals: + - kind: "vendor_metric" + vendor: + domain: "attentionvendor.example" + metric_id: "emissions_score" + priority: 1 + committed_metrics: + - scope: "vendor" + vendor: + domain: "attentionvendor.example" + metric_id: "emissions_score" + idempotency_key: "$generate:uuid_v4#media_buy_seller_vendor_metric_opt_bad_capability" + validations: + - check: error_code + allowed_values: ["INVALID_REQUEST"] + description: "Seller rejects unsupported (vendor, metric_id) with INVALID_REQUEST" + + - id: reject_missing_committed_metric + title: "Reject vendor_metric goal when package lacks vendor-scope committed_metrics entry" + narrative: | + The buyer submits a goal for attention_score (a valid capability on the + product) but omits the vendor-scope committed_metrics[] entry from the + package. Per the spec, optimization without committed reporting is + unverifiable and MUST be rejected with INVALID_REQUEST. + + This is a reporting-coherence precondition failure on the optimization + goal (governed by optimization-goal.json), not a committed_metrics + over-proposal rejection (which would be TERMS_REJECTED per + package-request.json). The distinction: TERMS_REJECTED covers a + committed_metrics[] entry the seller can't honor (e.g., the requested + metric exceeds product capability); INVALID_REQUEST covers an optimization + goal that is structurally valid but lacks the required committed_metrics + pairing. Runners that emit TERMS_REJECTED for this path are conflating the + two code paths — INVALID_REQUEST is the correct and expected code. + steps: + - id: create_media_buy_missing_committed + title: "Submit goal without matching vendor-scope committed_metrics entry" + 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" + comply_scenario: create_media_buy + expect_error: true + negative_path: payload_well_formed + stateful: false + expected: | + Reject with INVALID_REQUEST. The optimization goal targets attention_score + but the package's committed_metrics[] has no vendor-scope entry for + (attentionvendor.example, attention_score). Optimizing for a metric the + seller won't commit to reporting is disallowed — the reporting-coherence + precondition fails. media_buy_id is not allocated. + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + start_time: "2026-06-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "$context.product_id" + pricing_option_id: "$context.pricing_option_id" + budget: 5000 + optimization_goals: + - kind: "vendor_metric" + vendor: + domain: "attentionvendor.example" + metric_id: "attention_score" + target: + kind: "threshold_rate" + value: 70 + priority: 1 + idempotency_key: "$generate:uuid_v4#media_buy_seller_vendor_metric_opt_no_committed" + validations: + - check: error_code + allowed_values: ["INVALID_REQUEST"] + description: "Seller rejects goal missing reporting-coherence (no committed vendor-scope metric) with INVALID_REQUEST"