From 463207212a9a60aca50da44520fb4c38257e1537 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 8 Apr 2026 13:54:40 -0400 Subject: [PATCH 1/5] feat: promote 4 protocol storyboards from @adcp/client to canonical definitions Add 4 storyboards that define protocol compliance requirements: - schema_validation: response schema conformance + temporal constraints - behavioral_analysis: brief filtering, response consistency, pricing edge cases - error_compliance: error codes, recovery hints, transport bindings (L1/L2/L3) - media_buy_state_machine: state transitions + terminal state enforcement These were developed in @adcp/client but describe agent behavior requirements, not client internals. The adcp repo is the canonical source for protocol storyboards; @adcp/client will pull from here. Also updates PLATFORM_STORYBOARDS to include the new storyboards for all sales platforms (schema_validation, behavioral_analysis, error_compliance, media_buy_state_machine where applicable). 25 storyboards total. brand_rights already existed from #1985. deterministic_testing stays in @adcp/client (test harness, not protocol). Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/storyboards/behavioral_analysis.yaml | 197 ++++++++++++++ docs/storyboards/error_compliance.yaml | 225 ++++++++++++++++ docs/storyboards/media_buy_state_machine.yaml | 251 ++++++++++++++++++ docs/storyboards/schema.yaml | 2 +- docs/storyboards/schema_validation.yaml | 170 ++++++++++++ .../src/addie/services/compliance-testing.ts | 22 +- server/tests/unit/storyboards.test.ts | 6 +- 7 files changed, 860 insertions(+), 13 deletions(-) create mode 100644 docs/storyboards/behavioral_analysis.yaml create mode 100644 docs/storyboards/error_compliance.yaml create mode 100644 docs/storyboards/media_buy_state_machine.yaml create mode 100644 docs/storyboards/schema_validation.yaml diff --git a/docs/storyboards/behavioral_analysis.yaml b/docs/storyboards/behavioral_analysis.yaml new file mode 100644 index 0000000000..6e316bc018 --- /dev/null +++ b/docs/storyboards/behavioral_analysis.yaml @@ -0,0 +1,197 @@ +id: behavioral_analysis +version: "1.0.0" +title: "Behavioral analysis" +category: behavioral_analysis +summary: "Validates product discovery behavior: brief filtering, response consistency, and pricing edge cases." + +narrative: | + You expose a get_products task that accepts natural-language briefs and returns structured + product listings with pricing, delivery forecasts, and targeting. This storyboard verifies + three properties of that task: + + 1. Behavior analysis — do results change when the brief changes? Does the agent filter + products by brief content or return a static catalog? + 2. Response consistency — given the same brief twice, does the agent return the same + product IDs? Deterministic responses are expected for identical inputs. + 3. Pricing edge cases — do products declare valid pricing_options structures with + recognized delivery_type values (guaranteed or non_guaranteed)? + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + examples: + - "Yahoo" + - "Retail media networks" + - "Publisher platforms" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs a brand identity for product discovery requests. + The test kit provides a sample brand (Acme Outdoor) with campaign parameters. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: behavior_analysis + title: "Brief filtering behavior" + narrative: | + Tests whether the agent filters products based on the brief content. Two different + briefs are sent — one narrow (podcast audio only) and one broad (all products across + all channels). If the agent interprets briefs, the narrow brief should return fewer + products than the broad brief. + + steps: + - id: get_products_narrow + title: "Get products with narrow brief" + narrative: | + Send a narrow brief requesting only podcast audio advertising. Capture the + returned product IDs so they can be compared with a broader request. + 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: behavior_analysis + stateful: false + expected: | + Return products matching the narrow brief. If the agent interprets briefs, + this should return fewer products than a broad request. + + sample_request: + buying_mode: "brief" + brief: "Podcast audio advertising only — 30-second pre-roll spots" + brand: + domain: "acmeoutdoor.com" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains a products array" + + - id: get_products_broad + title: "Get products with broad brief" + narrative: | + Send a broad brief requesting all available products. Compare the result count + with the narrow brief — if the agent filters by brief, the broad request should + return more products. + 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: behavior_analysis + stateful: false + expected: | + Return all available products. The product count should be greater than or + equal to the narrow brief response. + + sample_request: + buying_mode: "brief" + brief: "Show me everything — all channels, all formats, all inventory" + brand: + domain: "acmeoutdoor.com" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + + - id: response_consistency + title: "Response consistency" + narrative: | + Tests whether the agent returns deterministic results for identical inputs. + The same brief is sent twice — the response should contain the same product IDs + in both cases. + + steps: + - id: get_products_first + title: "First request with identical brief" + narrative: | + Send a brief and capture the product IDs returned. + 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: response_consistency + stateful: false + expected: | + Return products matching the brief. Product IDs will be compared with a + subsequent identical request. + + sample_request: + buying_mode: "brief" + brief: "Premium video inventory on sports publishers. Adults 25-54." + brand: + domain: "acmeoutdoor.com" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + + - id: get_products_second + title: "Second request with identical brief" + narrative: | + Send the exact same brief again. The returned product IDs should match the + first response. + 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: response_consistency + stateful: false + expected: | + Return the same product IDs as the first request. Deterministic responses + are expected for identical inputs. + + sample_request: + buying_mode: "brief" + brief: "Premium video inventory on sports publishers. Adults 25-54." + brand: + domain: "acmeoutdoor.com" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + + - id: pricing_edge_cases + title: "Pricing structure validation" + narrative: | + Validates that products declare well-formed pricing_options with recognized + delivery_type values and complete pricing structures. + + steps: + - id: get_products_pricing + title: "Validate pricing structures" + narrative: | + Send a brief and inspect the pricing structures on returned products. + Every product must have a recognized delivery_type and valid pricing_options. + 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: pricing_edge_cases + stateful: false + expected: | + Products with valid pricing structures: + - delivery_type: "guaranteed" or "non_guaranteed" + - pricing_options with pricing_option_id and pricing_model + + sample_request: + buying_mode: "brief" + brief: "All available products with detailed pricing" + brand: + domain: "acmeoutdoor.com" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products[0].delivery_type" + description: "Products declare delivery_type" + - check: field_present + path: "products[0].pricing_options" + description: "Products include pricing_options" diff --git a/docs/storyboards/error_compliance.yaml b/docs/storyboards/error_compliance.yaml new file mode 100644 index 0000000000..ea54a857b8 --- /dev/null +++ b/docs/storyboards/error_compliance.yaml @@ -0,0 +1,225 @@ +id: error_compliance +version: "1.0.0" +title: "Error handling and compliance" +category: error_compliance +summary: "Validates that agents return properly structured AdCP errors with correct codes, recovery hints, and transport bindings." + +narrative: | + Every AdCP agent must handle invalid input gracefully. When a buyer sends a negative budget, + a nonexistent product ID, or a malformed request, the agent should return a structured error + response — not a stack trace or a generic 500. + + AdCP defines three compliance levels for error responses: + - L1: isError flag set on the MCP response + - L2: JSON text fallback with adcp_error object in content + - L3: structuredContent.adcp_error binding (full compliance) + + This storyboard sends intentionally bad input and validates that the agent rejects it with + the correct error code, recovery classification, and transport binding. Agents that use the + adcpError() helper from @adcp/client get L3 compliance automatically. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + examples: + - "Any AdCP seller agent" + +caller: + role: buyer_agent + example: "Compliance test harness" + +prerequisites: + description: | + No special prerequisites. The storyboard sends intentionally invalid requests + to test error handling. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: error_responses + title: "Error responses for invalid input" + narrative: | + The buyer sends requests with intentionally invalid data — negative budgets, missing + required fields, nonexistent product IDs. Each request should be rejected with a + structured error containing an appropriate AdCP error code. + + steps: + - id: negative_budget + title: "Reject negative budget" + narrative: | + A negative budget is never valid. The agent must reject this with INVALID_REQUEST + or BUDGET_TOO_LOW and a correctable recovery hint. + 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: error_handling + stateful: false + expected: | + Reject the request with an error containing: + - code: INVALID_REQUEST or BUDGET_TOO_LOW + - recovery: correctable + - field: packages[0].budget (optional but recommended) + + sample_request: + idempotency_key: "error-test-negative-budget" + start_time: "2026-05-01T00:00:00Z" + end_time: "2026-05-31T23:59:59Z" + packages: + - product_id: "test-product" + budget: -500 + pricing_option_id: "test-pricing" + + validations: + - check: field_present + path: "errors" + description: "Response contains an error for negative budget" + + - id: nonexistent_product + title: "Reject nonexistent product ID" + narrative: | + The buyer references a product ID that does not exist on this platform. + The agent should return PRODUCT_NOT_FOUND or INVALID_REQUEST. + 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: error_handling + stateful: false + expected: | + Reject the request with: + - code: PRODUCT_NOT_FOUND or INVALID_REQUEST + - recovery: correctable + + sample_request: + idempotency_key: "error-test-nonexistent-product" + start_time: "2026-05-01T00:00:00Z" + end_time: "2026-05-31T23:59:59Z" + packages: + - product_id: "nonexistent-product-id-xyz" + budget: 10000 + pricing_option_id: "test-pricing" + + validations: + - check: field_present + path: "errors" + description: "Response contains an error for nonexistent product" + + - id: missing_fields + title: "Reject empty get_products request" + narrative: | + Send a get_products request with no brief and no buying_mode. The agent should + either return products (treating it as a catalog browse) or return an error + requesting the missing brief. + 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: error_handling + stateful: false + expected: | + Either return products (treating empty request as browse) or return a + structured error requesting the missing brief. Both are valid. + + sample_request: + brand: + domain: "acmeoutdoor.com" + + validations: + - check: response_schema + description: "Response is either valid products or a structured error" + + - id: reversed_dates_error + title: "Reject reversed dates with error code" + narrative: | + Send a create_media_buy with end_time before start_time and verify the error + response includes the correct code and recovery classification. + 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: error_handling + stateful: false + expected: | + Reject with INVALID_REQUEST and correctable recovery. + + sample_request: + idempotency_key: "error-test-reversed-dates" + start_time: "2026-12-31T00:00:00Z" + end_time: "2026-01-01T00:00:00Z" + packages: + - product_id: "test-product" + budget: 10000 + pricing_option_id: "test-pricing" + + validations: + - check: field_present + path: "errors" + description: "Response contains an error for reversed dates" + + - id: error_structure + title: "Error structure compliance" + narrative: | + Validates that error responses follow the AdCP error structure with required + and optional fields. + + steps: + - id: validate_error_shape + title: "Validate error response structure" + narrative: | + Trigger an error and inspect the response structure. The adcp_error object + must have code and message. Optional fields include recovery, retry_after, + field, and suggestion. + 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: error_codes + stateful: false + expected: | + Error response with: + - code: a recognized AdCP error code + - message: human-readable description + - recovery: correctable, transient, or fatal (optional) + + sample_request: + idempotency_key: "error-structure-test" + start_time: "2026-12-31T00:00:00Z" + end_time: "2026-01-01T00:00:00Z" + packages: + - product_id: "test-product" + budget: -1 + pricing_option_id: "test-pricing" + + - id: error_transport + title: "Error transport bindings" + narrative: | + Validates that errors are transported correctly via MCP. The isError flag must + be set, and the error should be available in both JSON text content (L2) and + structuredContent (L3) bindings. + + steps: + - id: validate_transport_binding + title: "Validate error transport binding" + narrative: | + Trigger an error and verify the MCP transport binding: isError flag must be + set to true, and the error content should include the adcp_error object. + 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: error_transport + stateful: false + expected: | + MCP response with: + - isError: true + - content containing adcp_error (L2: JSON text, L3: structuredContent) + + sample_request: + idempotency_key: "error-transport-test" + start_time: "2026-12-31T00:00:00Z" + end_time: "2026-01-01T00:00:00Z" + packages: + - product_id: "nonexistent" + budget: -1 + pricing_option_id: "bad-pricing" diff --git a/docs/storyboards/media_buy_state_machine.yaml b/docs/storyboards/media_buy_state_machine.yaml new file mode 100644 index 0000000000..ee7eff0a83 --- /dev/null +++ b/docs/storyboards/media_buy_state_machine.yaml @@ -0,0 +1,251 @@ +id: media_buy_state_machine +version: "1.0.0" +title: "Media buy state machine lifecycle" +category: media_buy_state_machine +summary: "Validates media buy state transitions: create, pause, resume, cancel, and terminal state enforcement." + +narrative: | + A media buy has a well-defined state machine: draft, pending_approval, confirmed, active, + paused, completed, canceled. Transitions between states must follow the spec — you cannot + resume a canceled buy or pause a completed one. + + This storyboard creates a media buy, walks it through pause/resume/cancel transitions, then + verifies that the agent rejects updates to a buy in a terminal state (canceled or completed). + It also tests package-level pause/resume independent of the media buy status. + + The state machine is the backbone of media buy reliability. If an agent allows invalid + transitions, buyers cannot trust the status field and automation breaks down. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + examples: + - "Any AdCP seller with media buy support" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs a brand identity for account setup. The test creates a media buy + using discovered products, then exercises state transitions. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: setup + title: "Create a media buy" + narrative: | + Discover products and create a media buy to use for state transition testing. + The media buy ID is captured and passed to subsequent phases. + + steps: + - id: discover_products + title: "Discover products for media buy" + narrative: | + Send a brief to get available products with pricing options. The first product + with a pricing option will be used to create the test media buy. + 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 products with: + - product_id + - pricing_options with pricing_option_id + + sample_request: + buying_mode: "brief" + brief: "Display advertising products for state machine testing" + brand: + domain: "acmeoutdoor.com" + + 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 with a product_id" + + - id: create_buy + title: "Create the test media buy" + narrative: | + Create a media buy using the discovered product. This buy will be used for + all subsequent state transition tests. + 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: | + Return a confirmed media buy with: + - media_buy_id + - status: confirmed or active + + sample_request: + brand: + domain: "acmeoutdoor.com" + start_time: "2026-05-01T00:00:00Z" + end_time: "2026-05-31T23:59:59Z" + packages: + - product_id: "test-product" + budget: 10000 + pricing_option_id: "test-pricing" + + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Response includes a media_buy_id" + + - id: state_transitions + title: "Valid state transitions" + narrative: | + Exercise the valid state transitions: pause, resume, and cancel. Each transition + must update the status field correctly. + + steps: + - id: pause_buy + title: "Pause the media buy" + narrative: | + Pause the active media buy. The status should transition to paused. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Media buy status transitions to paused. + + sample_request: + media_buy_id: "test-media-buy" + paused: true + + validations: + - check: field_present + path: "status" + description: "Response includes updated status" + + - id: resume_buy + title: "Resume the media buy" + narrative: | + Resume the paused media buy. The status should transition back to active. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Media buy status transitions back to active. + + sample_request: + media_buy_id: "test-media-buy" + paused: false + + validations: + - check: field_present + path: "status" + description: "Response includes updated status" + + - id: cancel_buy + title: "Cancel the media buy" + narrative: | + Cancel the media buy. This is a terminal transition — the buy cannot be + resumed or modified after cancellation. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Media buy status transitions to canceled. This is terminal. + + sample_request: + media_buy_id: "test-media-buy" + canceled: true + + validations: + - check: field_present + path: "status" + description: "Response includes canceled status" + + - id: terminal_enforcement + title: "Terminal state enforcement" + narrative: | + Verify that the agent rejects state transitions on a canceled media buy. Pausing, + resuming, or re-canceling a terminal buy must return an error. + + steps: + - id: pause_canceled_buy + title: "Reject pause on canceled buy" + narrative: | + Attempt to pause a canceled media buy. The agent must reject this with + INVALID_STATE_TRANSITION. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: terminal_state_enforcement + stateful: true + expected: | + Reject with INVALID_STATE_TRANSITION — cannot pause a canceled buy. + + sample_request: + media_buy_id: "test-media-buy" + paused: true + + validations: + - check: field_present + path: "errors" + description: "Response contains an error for invalid transition" + + - id: resume_canceled_buy + title: "Reject resume on canceled buy" + narrative: | + Attempt to resume a canceled media buy. The agent must reject this with + INVALID_STATE_TRANSITION. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: terminal_state_enforcement + stateful: true + expected: | + Reject with INVALID_STATE_TRANSITION — cannot resume a canceled buy. + + sample_request: + media_buy_id: "test-media-buy" + paused: false + + validations: + - check: field_present + path: "errors" + description: "Response contains an error for invalid transition" + + - id: recancel_buy + title: "Handle re-cancel of canceled buy" + narrative: | + Attempt to cancel an already-canceled media buy. The agent should either + return an error or accept it idempotently. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: terminal_state_enforcement + stateful: true + expected: | + Either reject with INVALID_STATE_TRANSITION or accept idempotently. + Both behaviors are valid. + + sample_request: + media_buy_id: "test-media-buy" + canceled: true diff --git a/docs/storyboards/schema.yaml b/docs/storyboards/schema.yaml index dc9627339f..d5d36b016e 100644 --- a/docs/storyboards/schema.yaml +++ b/docs/storyboards/schema.yaml @@ -13,7 +13,7 @@ # id: string (unique identifier, e.g., "creative_template") # version: string (semver, e.g., "1.0.0") # title: string (human-readable title) -# category: enum (capability_discovery | creative_template | creative_ad_server | creative_sales_agent | creative_generative | creative_lifecycle | media_buy_seller | media_buy_guaranteed_approval | media_buy_non_guaranteed | media_buy_proposal_mode | media_buy_governance_escalation | media_buy_catalog_creative | campaign_governance_denied | campaign_governance_conditions | campaign_governance_delivery | signal_marketplace | signal_owned | social_platform | si_session | brand_rights | property_governance | content_standards) +# category: enum (capability_discovery | schema_validation | behavioral_analysis | error_compliance | creative_template | creative_ad_server | creative_sales_agent | creative_generative | creative_lifecycle | media_buy_seller | media_buy_guaranteed_approval | media_buy_non_guaranteed | media_buy_proposal_mode | media_buy_governance_escalation | media_buy_catalog_creative | media_buy_state_machine | campaign_governance_denied | campaign_governance_conditions | campaign_governance_delivery | signal_marketplace | signal_owned | social_platform | si_session | brand_rights | property_governance | content_standards) # summary: string (one-line description for listings) # narrative: string (paragraph explaining the overall flow) # diff --git a/docs/storyboards/schema_validation.yaml b/docs/storyboards/schema_validation.yaml new file mode 100644 index 0000000000..dd1e8bb091 --- /dev/null +++ b/docs/storyboards/schema_validation.yaml @@ -0,0 +1,170 @@ +id: schema_validation +version: "1.0.0" +title: "Schema compliance and temporal validation" +category: schema_validation +summary: "Validates that agent responses conform to AdCP schemas and that temporal constraints are enforced." + +narrative: | + Every AdCP agent must return responses that match the published JSON schemas. Fields defined + in the spec — product_id, delivery_type, pricing_options — must be present and correctly typed. + Agents must also enforce temporal constraints: flight dates must be logically consistent + (end after start) and start dates should not be in the past. + + This storyboard checks schema compliance on get_products responses and validates that + create_media_buy rejects temporally invalid requests. These are foundational requirements + that every agent must satisfy regardless of platform type. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + examples: + - "Any AdCP seller agent" + +caller: + role: buyer_agent + example: "Compliance test harness" + +prerequisites: + description: | + The caller needs a brand identity for product discovery requests. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: schema_compliance + title: "Response schema compliance" + narrative: | + The buyer sends a standard get_products request and validates that the response + matches the published schema. Each product must have the required fields defined + in the AdCP spec. + + steps: + - id: get_products_schema + title: "Validate get_products response schema" + narrative: | + Send a brief and validate the response structure. Every product in the response + must conform to the get-products-response.json schema, with required v3 fields + present. + 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: schema_compliance + stateful: false + expected: | + Return products matching the brief. Each product must have: + - product_id: unique identifier + - name: human-readable name + - delivery_type: guaranteed or non_guaranteed + - pricing_options: at least one pricing option + + sample_request: + buying_mode: "brief" + brief: "Show all available advertising products" + brand: + domain: "acmeoutdoor.com" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains a products array" + - check: field_present + path: "products[0].product_id" + description: "Each product has a product_id" + - check: field_present + path: "products[0].delivery_type" + description: "Each product declares guaranteed or non_guaranteed delivery" + + - id: pricing_options_present + title: "Validate pricing options structure" + narrative: | + Verify that products include pricing_options with the required fields: + pricing_option_id and pricing_model. These are required for buyers to + construct valid create_media_buy requests. + 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: schema_compliance + stateful: false + expected: | + Products include pricing_options with: + - pricing_option_id: unique identifier for the pricing option + - pricing_model: CPM, CPC, flat_rate, etc. + + sample_request: + buying_mode: "brief" + brief: "Premium display inventory with pricing details" + brand: + domain: "acmeoutdoor.com" + + validations: + - check: field_present + path: "products[0].pricing_options[0].pricing_option_id" + description: "Pricing options have a pricing_option_id" + - check: field_present + path: "products[0].pricing_options[0].pricing_model" + description: "Pricing options declare a pricing_model" + + - id: temporal_validation + title: "Temporal constraint enforcement" + narrative: | + The buyer sends media buy requests with invalid temporal constraints. The agent + must reject reversed dates (end before start) and handle past start dates + appropriately. + + steps: + - id: reversed_dates + title: "Reject reversed flight dates" + narrative: | + Send a create_media_buy request where end_time is before start_time. The agent + must reject this with an INVALID_REQUEST error. + 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: temporal_validation + stateful: false + expected: | + Reject the request with: + - Error code: INVALID_REQUEST + - Message indicating the date constraint violation + + sample_request: + start_time: "2026-12-31T00:00:00Z" + end_time: "2026-01-01T00:00:00Z" + packages: + - product_id: "test-product" + budget: 10000 + pricing_option_id: "test-pricing" + + validations: + - check: field_present + path: "errors" + description: "Response contains an error for reversed dates" + + - id: past_start_date + title: "Handle past start date" + narrative: | + Send a create_media_buy request with a start_time in the past. The agent + should either reject the request or auto-adjust the start date forward. + 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: temporal_validation + stateful: false + expected: | + Either reject with INVALID_REQUEST or accept with adjusted dates. + Both behaviors are valid — the key is that the agent does not silently + accept a past start date without acknowledgment. + + sample_request: + start_time: "2020-01-01T00:00:00Z" + end_time: "2026-12-31T23:59:59Z" + packages: + - product_id: "test-product" + budget: 10000 + pricing_option_id: "test-pricing" diff --git a/server/src/addie/services/compliance-testing.ts b/server/src/addie/services/compliance-testing.ts index ba67f55399..df105907af 100644 --- a/server/src/addie/services/compliance-testing.ts +++ b/server/src/addie/services/compliance-testing.ts @@ -275,21 +275,21 @@ const PLATFORM_PROFILES: Record = { * storyboards extract to scenarios, scenarios run via testAllScenarios(). */ export const PLATFORM_STORYBOARDS: Record = { - display_ad_server: ['capability_discovery', 'media_buy_seller'], - video_ad_server: ['capability_discovery', 'media_buy_seller'], - social_platform: ['capability_discovery', 'social_platform'], - pmax_platform: ['capability_discovery', 'media_buy_seller', 'creative_lifecycle'], - dsp: ['capability_discovery', 'media_buy_seller'], - retail_media: ['capability_discovery', 'media_buy_seller', 'media_buy_catalog_creative'], - search_platform: ['capability_discovery', 'media_buy_seller'], - audio_platform: ['capability_discovery', 'media_buy_seller'], + display_ad_server: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'error_compliance'], + video_ad_server: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'error_compliance'], + social_platform: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'social_platform', 'error_compliance'], + pmax_platform: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'creative_lifecycle', 'error_compliance'], + dsp: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'error_compliance'], + retail_media: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_catalog_creative', 'error_compliance'], + search_platform: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'error_compliance'], + audio_platform: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'error_compliance'], creative_transformer: ['capability_discovery', 'creative_template'], creative_library: ['capability_discovery', 'creative_lifecycle'], creative_ad_server: ['capability_discovery', 'creative_ad_server'], si_platform: ['capability_discovery', 'si_session'], - ai_ad_network: ['capability_discovery', 'media_buy_seller', 'creative_lifecycle'], - ai_platform: ['capability_discovery', 'creative_template'], - generative_dsp: ['capability_discovery', 'media_buy_seller', 'creative_lifecycle'], + ai_ad_network: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'creative_lifecycle', 'error_compliance'], + ai_platform: ['capability_discovery', 'schema_validation', 'creative_template'], + generative_dsp: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'creative_lifecycle', 'error_compliance'], }; export function getPlatformStoryboards(platformType: PlatformType): string[] | undefined { diff --git a/server/tests/unit/storyboards.test.ts b/server/tests/unit/storyboards.test.ts index 077da6cb83..cd9fa3a3ba 100644 --- a/server/tests/unit/storyboards.test.ts +++ b/server/tests/unit/storyboards.test.ts @@ -12,10 +12,14 @@ import { describe('listStoryboards', () => { it('returns all storyboards when no category filter', () => { const results = listStoryboards(); - expect(results.length).toBeGreaterThanOrEqual(21); + expect(results.length).toBeGreaterThanOrEqual(25); const ids = results.map((s) => s.id); expect(ids).toContain('capability_discovery'); + expect(ids).toContain('schema_validation'); + expect(ids).toContain('behavioral_analysis'); + expect(ids).toContain('error_compliance'); + expect(ids).toContain('media_buy_state_machine'); expect(ids).toContain('creative_template'); expect(ids).toContain('creative_ad_server'); expect(ids).toContain('creative_sales_agent'); From fc7e983b08f0650e90f699ac7992457728ba419d Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 8 Apr 2026 13:55:04 -0400 Subject: [PATCH 2/5] chore: add empty changeset Co-Authored-By: Claude Opus 4.6 (1M context) --- .changeset/deep-baboons-build.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changeset/deep-baboons-build.md diff --git a/.changeset/deep-baboons-build.md b/.changeset/deep-baboons-build.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/deep-baboons-build.md @@ -0,0 +1,2 @@ +--- +--- From 7899f921d343d5402395ecc559844b39acdb72c5 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 8 Apr 2026 14:00:08 -0400 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20address=20code=20review=20=E2=80=94?= =?UTF-8?q?=20consistent=20platform=20mappings=20and=20per-storyboard=20te?= =?UTF-8?q?sts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add media_buy_state_machine to all media-buy-capable platforms (retail_media, search_platform, audio_platform, ai_ad_network, social_platform) - Add behavioral_analysis and error_compliance to ai_platform - Add per-storyboard test blocks for schema_validation, behavioral_analysis, error_compliance, and media_buy_state_machine Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/addie/services/compliance-testing.ts | 12 ++-- server/tests/unit/storyboards.test.ts | 65 +++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/server/src/addie/services/compliance-testing.ts b/server/src/addie/services/compliance-testing.ts index df105907af..f733ae3d8d 100644 --- a/server/src/addie/services/compliance-testing.ts +++ b/server/src/addie/services/compliance-testing.ts @@ -277,18 +277,18 @@ const PLATFORM_PROFILES: Record = { export const PLATFORM_STORYBOARDS: Record = { display_ad_server: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'error_compliance'], video_ad_server: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'error_compliance'], - social_platform: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'social_platform', 'error_compliance'], + social_platform: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'social_platform', 'media_buy_state_machine', 'error_compliance'], pmax_platform: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'creative_lifecycle', 'error_compliance'], dsp: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'error_compliance'], - retail_media: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_catalog_creative', 'error_compliance'], - search_platform: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'error_compliance'], - audio_platform: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'error_compliance'], + retail_media: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_catalog_creative', 'media_buy_state_machine', 'error_compliance'], + search_platform: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'error_compliance'], + audio_platform: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'error_compliance'], creative_transformer: ['capability_discovery', 'creative_template'], creative_library: ['capability_discovery', 'creative_lifecycle'], creative_ad_server: ['capability_discovery', 'creative_ad_server'], si_platform: ['capability_discovery', 'si_session'], - ai_ad_network: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'creative_lifecycle', 'error_compliance'], - ai_platform: ['capability_discovery', 'schema_validation', 'creative_template'], + ai_ad_network: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'creative_lifecycle', 'error_compliance'], + ai_platform: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'creative_template', 'error_compliance'], generative_dsp: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'creative_lifecycle', 'error_compliance'], }; diff --git a/server/tests/unit/storyboards.test.ts b/server/tests/unit/storyboards.test.ts index cd9fa3a3ba..083b883c35 100644 --- a/server/tests/unit/storyboards.test.ts +++ b/server/tests/unit/storyboards.test.ts @@ -654,3 +654,68 @@ describe('content_standards storyboard', () => { expect(kit!.id).toBe('acme_outdoor'); }); }); + +describe('schema_validation storyboard', () => { + it('covers schema compliance and temporal validation', () => { + const sb = getStoryboard('schema_validation')!; + expect(sb).toBeDefined(); + const phaseIds = sb.phases.map((p) => p.id); + expect(phaseIds).toContain('schema_compliance'); + expect(phaseIds).toContain('temporal_validation'); + const tasks = sb.phases.flatMap((p) => p.steps.map((s) => s.task)); + expect(tasks).toContain('get_products'); + expect(tasks).toContain('create_media_buy'); + }); +}); + +describe('behavioral_analysis storyboard', () => { + it('covers brief filtering, consistency, and pricing edge cases', () => { + const sb = getStoryboard('behavioral_analysis')!; + expect(sb).toBeDefined(); + const phaseIds = sb.phases.map((p) => p.id); + expect(phaseIds).toContain('behavior_analysis'); + expect(phaseIds).toContain('response_consistency'); + expect(phaseIds).toContain('pricing_edge_cases'); + }); + + it('resolves acme_outdoor test kit', () => { + const kit = getTestKitForStoryboard('behavioral_analysis'); + expect(kit).toBeDefined(); + expect(kit!.id).toBe('acme_outdoor'); + }); +}); + +describe('error_compliance storyboard', () => { + it('covers error responses, structure, and transport bindings', () => { + const sb = getStoryboard('error_compliance')!; + expect(sb).toBeDefined(); + const phaseIds = sb.phases.map((p) => p.id); + expect(phaseIds).toContain('error_responses'); + expect(phaseIds).toContain('error_structure'); + expect(phaseIds).toContain('error_transport'); + const tasks = sb.phases.flatMap((p) => p.steps.map((s) => s.task)); + expect(tasks).toContain('create_media_buy'); + expect(tasks).toContain('get_products'); + }); +}); + +describe('media_buy_state_machine storyboard', () => { + it('covers state transitions and terminal enforcement', () => { + const sb = getStoryboard('media_buy_state_machine')!; + expect(sb).toBeDefined(); + const phaseIds = sb.phases.map((p) => p.id); + expect(phaseIds).toContain('setup'); + expect(phaseIds).toContain('state_transitions'); + expect(phaseIds).toContain('terminal_enforcement'); + const tasks = sb.phases.flatMap((p) => p.steps.map((s) => s.task)); + expect(tasks).toContain('get_products'); + expect(tasks).toContain('create_media_buy'); + expect(tasks).toContain('update_media_buy'); + }); + + it('resolves acme_outdoor test kit', () => { + const kit = getTestKitForStoryboard('media_buy_state_machine'); + expect(kit).toBeDefined(); + expect(kit!.id).toBe('acme_outdoor'); + }); +}); From e3806d7423499282a5776b982d6800ab5b7aea68 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 8 Apr 2026 14:09:16 -0400 Subject: [PATCH 4/5] fix: add track, required_tools, platform_types to all storyboards All 25 storyboards now include: - track: compliance track for grouping results (core, products, media_buy, etc.) - required_tools: tools the agent must advertise for the storyboard to run - platform_types: (where applicable) which platform types this storyboard applies to These fields are required by @adcp/client's loader functions (getStoryboardsForPlatformType, getComplianceStoryboardsForTrack). Without them, the client cannot filter or group storyboards. Also updates the Storyboard TypeScript interface to include the new fields. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/storyboards/behavioral_analysis.yaml | 15 +++++++++++++++ docs/storyboards/brand_rights.yaml | 3 +++ .../campaign_governance_conditions.yaml | 4 ++++ .../storyboards/campaign_governance_delivery.yaml | 3 +++ docs/storyboards/campaign_governance_denied.yaml | 4 ++++ docs/storyboards/capability_discovery.yaml | 1 + docs/storyboards/content_standards.yaml | 3 +++ docs/storyboards/creative_ad_server.yaml | 3 +++ docs/storyboards/creative_lifecycle.yaml | 3 +++ docs/storyboards/creative_sales_agent.yaml | 3 +++ docs/storyboards/creative_template.yaml | 3 +++ docs/storyboards/error_compliance.yaml | 3 +++ docs/storyboards/media_buy_catalog_creative.yaml | 4 ++++ .../media_buy_governance_escalation.yaml | 4 ++++ .../media_buy_guaranteed_approval.yaml | 4 ++++ docs/storyboards/media_buy_non_guaranteed.yaml | 4 ++++ docs/storyboards/media_buy_proposal_mode.yaml | 4 ++++ docs/storyboards/media_buy_seller.yaml | 4 ++++ docs/storyboards/media_buy_state_machine.yaml | 4 ++++ docs/storyboards/property_governance.yaml | 3 +++ docs/storyboards/schema_validation.yaml | 3 +++ docs/storyboards/si_session.yaml | 3 +++ docs/storyboards/signal_marketplace.yaml | 3 +++ docs/storyboards/signal_owned.yaml | 3 +++ docs/storyboards/social_platform.yaml | 3 +++ server/src/services/storyboards.ts | 3 +++ 26 files changed, 97 insertions(+) diff --git a/docs/storyboards/behavioral_analysis.yaml b/docs/storyboards/behavioral_analysis.yaml index 6e316bc018..859dde9e68 100644 --- a/docs/storyboards/behavioral_analysis.yaml +++ b/docs/storyboards/behavioral_analysis.yaml @@ -3,6 +3,21 @@ version: "1.0.0" title: "Behavioral analysis" category: behavioral_analysis summary: "Validates product discovery behavior: brief filtering, response consistency, and pricing edge cases." +track: products +required_tools: + - get_products +platform_types: + - display_ad_server + - video_ad_server + - social_platform + - retail_media + - search_platform + - audio_platform + - dsp + - pmax_platform + - ai_ad_network + - ai_platform + - generative_dsp narrative: | You expose a get_products task that accepts natural-language briefs and returns structured diff --git a/docs/storyboards/brand_rights.yaml b/docs/storyboards/brand_rights.yaml index ea2c432065..49d2e2adbe 100644 --- a/docs/storyboards/brand_rights.yaml +++ b/docs/storyboards/brand_rights.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Brand identity and rights licensing" category: brand_rights summary: "Brand agent that serves identity assets and licenses rights for AI-generated content." +track: core +required_tools: + - get_brand_identity narrative: | You run a brand agent — a system that holds brand identity data (logos, colors, fonts, diff --git a/docs/storyboards/campaign_governance_conditions.yaml b/docs/storyboards/campaign_governance_conditions.yaml index 8dc749a3e6..9991094dcd 100644 --- a/docs/storyboards/campaign_governance_conditions.yaml +++ b/docs/storyboards/campaign_governance_conditions.yaml @@ -3,6 +3,10 @@ version: "1.0.0" title: "Campaign governance — conditional approval" category: campaign_governance_conditions summary: "Governance agent approves a media buy with conditions. Buyer re-checks after meeting the conditions." +track: campaign_governance +required_tools: + - sync_plans + - check_governance narrative: | The buyer's governance agent evaluates a media buy that falls within spending authority but diff --git a/docs/storyboards/campaign_governance_delivery.yaml b/docs/storyboards/campaign_governance_delivery.yaml index bcabd9b741..79d01e7515 100644 --- a/docs/storyboards/campaign_governance_delivery.yaml +++ b/docs/storyboards/campaign_governance_delivery.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Campaign governance — delivery monitoring with drift detection" category: campaign_governance_delivery summary: "Governance agent monitors delivery, detects budget drift past thresholds, and triggers re-evaluation." +track: campaign_governance +required_tools: + - check_governance narrative: | After a media buy is confirmed with governance approval, the governance agent monitors diff --git a/docs/storyboards/campaign_governance_denied.yaml b/docs/storyboards/campaign_governance_denied.yaml index d9f856bd1d..c74a835be7 100644 --- a/docs/storyboards/campaign_governance_denied.yaml +++ b/docs/storyboards/campaign_governance_denied.yaml @@ -3,6 +3,10 @@ version: "1.0.0" title: "Campaign governance — denied" category: campaign_governance_denied summary: "Governance agent denies a media buy that exceeds the agent's spending authority. No human escalation — the buy is blocked." +track: campaign_governance +required_tools: + - sync_plans + - check_governance narrative: | The buyer's governance agent registers a plan with strict spending authority. The buyer diff --git a/docs/storyboards/capability_discovery.yaml b/docs/storyboards/capability_discovery.yaml index aad1cf631e..17c5571baf 100644 --- a/docs/storyboards/capability_discovery.yaml +++ b/docs/storyboards/capability_discovery.yaml @@ -3,6 +3,7 @@ version: "1.0.0" title: "Capability discovery" category: capability_discovery summary: "Buyer calls get_adcp_capabilities to discover what an agent supports before making any buying or creative decisions." +track: core narrative: | Before doing anything else, a buyer agent calls get_adcp_capabilities to learn what your diff --git a/docs/storyboards/content_standards.yaml b/docs/storyboards/content_standards.yaml index 7a86bc0a00..4f361516b4 100644 --- a/docs/storyboards/content_standards.yaml +++ b/docs/storyboards/content_standards.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Content standards" category: content_standards summary: "Define creative quality rules, calibrate content against them, and validate that delivered ads met the standards." +track: governance +required_tools: + - list_content_standards narrative: | You run a governance agent that manages content standards — rules that define what diff --git a/docs/storyboards/creative_ad_server.yaml b/docs/storyboards/creative_ad_server.yaml index 998f51b3fb..0f621088ae 100644 --- a/docs/storyboards/creative_ad_server.yaml +++ b/docs/storyboards/creative_ad_server.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Creative ad server" category: creative_ad_server summary: "Stateful ad server with pre-loaded creatives. Generates serving tags per media buy." +track: creative +required_tools: + - build_creative narrative: | You run a creative ad server — think Innovid, Flashtalking, or CM360. Your clients diff --git a/docs/storyboards/creative_lifecycle.yaml b/docs/storyboards/creative_lifecycle.yaml index b836435f30..27aa1eb1d0 100644 --- a/docs/storyboards/creative_lifecycle.yaml +++ b/docs/storyboards/creative_lifecycle.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Creative lifecycle" category: creative_lifecycle summary: "Full creative lifecycle on a stateful platform: sync multiple creatives, list with filtering, build and preview across formats, observe status transitions." +track: creative +required_tools: + - list_creative_formats narrative: | You run a creative platform with a persistent library — an ad server, creative management diff --git a/docs/storyboards/creative_sales_agent.yaml b/docs/storyboards/creative_sales_agent.yaml index 6f37b1cacd..d7bbe4d223 100644 --- a/docs/storyboards/creative_sales_agent.yaml +++ b/docs/storyboards/creative_sales_agent.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Sales agent with creative capabilities" category: creative_sales_agent summary: "Stateful sales agent that accepts pushed creative assets and renders them in its environment." +track: creative +required_tools: + - sync_creatives narrative: | You run a publisher platform, retail media network, or other sell-side system that diff --git a/docs/storyboards/creative_template.yaml b/docs/storyboards/creative_template.yaml index 2530b818c1..addd3ad37f 100644 --- a/docs/storyboards/creative_template.yaml +++ b/docs/storyboards/creative_template.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Creative template and transformation agent" category: creative_template summary: "Stateless creative agent that takes assets in, applies templates, and produces tags or rendered output." +track: creative +required_tools: + - build_creative narrative: | You build a creative management or rich media platform — think Celtra, a format diff --git a/docs/storyboards/error_compliance.yaml b/docs/storyboards/error_compliance.yaml index ea54a857b8..5e8a907b2f 100644 --- a/docs/storyboards/error_compliance.yaml +++ b/docs/storyboards/error_compliance.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Error handling and compliance" category: error_compliance summary: "Validates that agents return properly structured AdCP errors with correct codes, recovery hints, and transport bindings." +track: error_handling +required_tools: + - get_products narrative: | Every AdCP agent must handle invalid input gracefully. When a buyer sends a negative budget, diff --git a/docs/storyboards/media_buy_catalog_creative.yaml b/docs/storyboards/media_buy_catalog_creative.yaml index ac4f435eb6..37664c929c 100644 --- a/docs/storyboards/media_buy_catalog_creative.yaml +++ b/docs/storyboards/media_buy_catalog_creative.yaml @@ -3,6 +3,10 @@ version: "1.0.0" title: "Catalog-driven creative and conversion tracking" category: media_buy_catalog_creative summary: "Seller that renders dynamic ads from product catalogs, tracks conversions, and optimizes delivery based on performance feedback." +track: media_buy +required_tools: + - get_products + - create_media_buy narrative: | You run a platform that supports catalog-driven advertising — think Snap Dynamic Ads, diff --git a/docs/storyboards/media_buy_governance_escalation.yaml b/docs/storyboards/media_buy_governance_escalation.yaml index c10c5e90ff..f6ea9c6a34 100644 --- a/docs/storyboards/media_buy_governance_escalation.yaml +++ b/docs/storyboards/media_buy_governance_escalation.yaml @@ -3,6 +3,10 @@ version: "1.0.0" title: "Governance denial and human escalation" category: media_buy_governance_escalation summary: "Buyer's governance agent denies a media buy that exceeds spending authority, escalates to a human who approves with conditions." +track: campaign_governance +required_tools: + - sync_plans + - check_governance narrative: | The buyer's governance agent denies a media buy because it exceeds the agent's spending diff --git a/docs/storyboards/media_buy_guaranteed_approval.yaml b/docs/storyboards/media_buy_guaranteed_approval.yaml index 22c38194e8..731232e111 100644 --- a/docs/storyboards/media_buy_guaranteed_approval.yaml +++ b/docs/storyboards/media_buy_guaranteed_approval.yaml @@ -3,6 +3,10 @@ version: "1.0.0" title: "Guaranteed media buy with human IO approval" category: media_buy_guaranteed_approval summary: "Seller agent that requires human-in-the-loop IO signing before guaranteed media buys go live." +track: media_buy +required_tools: + - get_products + - create_media_buy narrative: | You run a sell-side platform that requires human approval before guaranteed media buys go diff --git a/docs/storyboards/media_buy_non_guaranteed.yaml b/docs/storyboards/media_buy_non_guaranteed.yaml index 1a77706b59..cb82e7972e 100644 --- a/docs/storyboards/media_buy_non_guaranteed.yaml +++ b/docs/storyboards/media_buy_non_guaranteed.yaml @@ -3,6 +3,10 @@ version: "1.0.0" title: "Non-guaranteed auction-based media buy" category: media_buy_non_guaranteed summary: "Seller agent for auction-based, non-guaranteed buying where the buyer sets bid prices and budgets." +track: media_buy +required_tools: + - get_products + - create_media_buy narrative: | You run a sell-side platform with auction-based inventory. Non-guaranteed buys don't diff --git a/docs/storyboards/media_buy_proposal_mode.yaml b/docs/storyboards/media_buy_proposal_mode.yaml index 336c2f983b..00ebdafa5f 100644 --- a/docs/storyboards/media_buy_proposal_mode.yaml +++ b/docs/storyboards/media_buy_proposal_mode.yaml @@ -3,6 +3,10 @@ version: "1.0.0" title: "Media buy via proposal acceptance" category: media_buy_proposal_mode summary: "Seller agent that generates curated media plan proposals the buyer can review, refine, and accept." +track: media_buy +required_tools: + - get_products + - create_media_buy narrative: | Your seller generates curated media plan proposals. The buyer sends a brief, your platform diff --git a/docs/storyboards/media_buy_seller.yaml b/docs/storyboards/media_buy_seller.yaml index 0cf72fe47e..7ec2da6b16 100644 --- a/docs/storyboards/media_buy_seller.yaml +++ b/docs/storyboards/media_buy_seller.yaml @@ -3,6 +3,10 @@ version: "1.0.0" title: "Media buy seller agent" category: media_buy_seller summary: "Seller agent that receives briefs, returns products, accepts media buys, and reports delivery." +track: media_buy +required_tools: + - get_products + - create_media_buy narrative: | You run a sell-side platform — a publisher, SSP, retail media network, or any system that diff --git a/docs/storyboards/media_buy_state_machine.yaml b/docs/storyboards/media_buy_state_machine.yaml index ee7eff0a83..b8ee5189e6 100644 --- a/docs/storyboards/media_buy_state_machine.yaml +++ b/docs/storyboards/media_buy_state_machine.yaml @@ -3,6 +3,10 @@ version: "1.0.0" title: "Media buy state machine lifecycle" category: media_buy_state_machine summary: "Validates media buy state transitions: create, pause, resume, cancel, and terminal state enforcement." +track: media_buy +required_tools: + - create_media_buy + - update_media_buy narrative: | A media buy has a well-defined state machine: draft, pending_approval, confirmed, active, diff --git a/docs/storyboards/property_governance.yaml b/docs/storyboards/property_governance.yaml index 584e63aebc..c15235aee7 100644 --- a/docs/storyboards/property_governance.yaml +++ b/docs/storyboards/property_governance.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Property governance" category: property_governance summary: "Manage brand safety property lists — create inclusion/exclusion lists, query and update them, validate delivery compliance." +track: governance +required_tools: + - create_property_list narrative: | You run a governance agent that manages property lists for brand safety. Buyers create diff --git a/docs/storyboards/schema_validation.yaml b/docs/storyboards/schema_validation.yaml index dd1e8bb091..18dd007c9e 100644 --- a/docs/storyboards/schema_validation.yaml +++ b/docs/storyboards/schema_validation.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Schema compliance and temporal validation" category: schema_validation summary: "Validates that agent responses conform to AdCP schemas and that temporal constraints are enforced." +track: core +required_tools: + - get_products narrative: | Every AdCP agent must return responses that match the published JSON schemas. Fields defined diff --git a/docs/storyboards/si_session.yaml b/docs/storyboards/si_session.yaml index 14227fddef..18128f0fa0 100644 --- a/docs/storyboards/si_session.yaml +++ b/docs/storyboards/si_session.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Sponsored intelligence session" category: si_session summary: "Conversational ad session on an AI platform — discover offerings, initiate a session, exchange messages, and terminate." +track: si +required_tools: + - si_initiate_session narrative: | You run an AI platform that supports sponsored intelligence — conversational ad experiences diff --git a/docs/storyboards/signal_marketplace.yaml b/docs/storyboards/signal_marketplace.yaml index 668b1c07c3..3bf23a83ce 100644 --- a/docs/storyboards/signal_marketplace.yaml +++ b/docs/storyboards/signal_marketplace.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Marketplace signal agent" category: signal_marketplace summary: "Signal agent that resells third-party data provider signals with verifiable catalog provenance." +track: signals +required_tools: + - get_signals narrative: | You operate a signal marketplace — an intermediary that aggregates audience data from diff --git a/docs/storyboards/signal_owned.yaml b/docs/storyboards/signal_owned.yaml index 57e0eb1b5d..d7cd2752f8 100644 --- a/docs/storyboards/signal_owned.yaml +++ b/docs/storyboards/signal_owned.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Owned signal agent" category: signal_owned summary: "Signal agent serving first-party or proprietary audience data without external catalog verification." +track: signals +required_tools: + - get_signals narrative: | You operate a first-party data platform — a retailer CDP, publisher contextual data diff --git a/docs/storyboards/social_platform.yaml b/docs/storyboards/social_platform.yaml index 4ba215a1cc..182aea0d84 100644 --- a/docs/storyboards/social_platform.yaml +++ b/docs/storyboards/social_platform.yaml @@ -3,6 +3,9 @@ version: "1.0.0" title: "Social platform" category: social_platform summary: "Social media platform that accepts audience segments, native creatives, and conversion events from buyer agents." +track: audiences +required_tools: + - sync_audiences narrative: | You run a social media platform — Snap, Meta, TikTok, Pinterest, or any walled garden that diff --git a/server/src/services/storyboards.ts b/server/src/services/storyboards.ts index 118b789f9d..01109acfdf 100644 --- a/server/src/services/storyboards.ts +++ b/server/src/services/storyboards.ts @@ -66,6 +66,9 @@ export interface Storyboard { title: string; category: string; summary: string; + track?: string; + required_tools?: string[]; + platform_types?: string[]; narrative: string; agent: StoryboardAgent; caller: StoryboardCaller; From bedf1d1c8145bb274c15d5c0e55de3dac91f81f2 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 8 Apr 2026 14:26:06 -0400 Subject: [PATCH 5/5] chore: mark PLATFORM_STORYBOARDS as deprecated This mapping will move to @adcp/client once adcp-client#445 ships storyboard-based comply(). Callers will pass platform_type to comply() and let the client resolve storyboards internally. Co-Authored-By: Claude Opus 4.6 (1M context) --- server/src/addie/services/compliance-testing.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/src/addie/services/compliance-testing.ts b/server/src/addie/services/compliance-testing.ts index f733ae3d8d..9ca089b4bc 100644 --- a/server/src/addie/services/compliance-testing.ts +++ b/server/src/addie/services/compliance-testing.ts @@ -271,8 +271,11 @@ const PLATFORM_PROFILES: Record = { /** * Maps each platform type to the storyboards that define its expected behavior. - * This is the primary routing mechanism: a platform type selects storyboards, - * storyboards extract to scenarios, scenarios run via testAllScenarios(). + * + * @deprecated This mapping will move to @adcp/client once adcp-client#445 + * ships storyboard-based comply(). At that point, callers should pass + * platform_type to comply() and let the client resolve storyboards internally. + * The canonical mapping will live next to comply() in the client package. */ export const PLATFORM_STORYBOARDS: Record = { display_ad_server: ['capability_discovery', 'schema_validation', 'behavioral_analysis', 'media_buy_seller', 'media_buy_state_machine', 'error_compliance'],