Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changeset/vendor-metric-optimization-storyboard.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions server/src/training-agent/product-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ function buildProduct(
} as NonNullable<Product['reporting_capabilities']>,
...(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 }),
Expand Down
10 changes: 10 additions & 0 deletions server/src/training-agent/publishers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
8 changes: 8 additions & 0 deletions server/src/training-agent/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
Loading