Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .changeset/comply-controller-mode-gate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"adcontextprotocol": patch
---

Add `comply_controller_mode_gate` storyboard and `acme-outdoor-live` test kit. Exercises the comply_test_controller live-mode denial gate — verifies sellers return FORBIDDEN when a live-mode account invokes the controller, closing the fail-open gap where all existing storyboards use sandbox principals only.
1 change: 1 addition & 0 deletions dist/docs/3.0.1/building/compliance-catalog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Every agent runs every storyboard in `/compliance/{version}/universal/` regardle
| `collection-lists-pagination-integrity` | `cursor` ↔ `has_more` invariant verified by walking a paginated `list_collection_lists` response — storyboard bootstraps three collection lists via `create_collection_list`, then asserts page 1 is non-terminal and page 2 is terminal with no stale cursor. |
| `property-lists-pagination-integrity` | `cursor` ↔ `has_more` invariant verified by walking a paginated `list_property_lists` response — storyboard bootstraps three property lists via `create_property_list`, then asserts page 1 is non-terminal and page 2 is terminal with no stale cursor. |
| `deterministic-testing` | `comply_test_controller` state-machine verification — skipped if `capabilities.compliance_testing.supported: false`. |
| `comply-controller-mode-gate` | `comply_test_controller` live-mode denial gate — verifies sellers return `FORBIDDEN` when a live-mode account calls the controller; skipped for agents that do not expose `comply_test_controller`. |
| `signed-requests` | RFC 9421 transport-layer request-signing verification — skipped if `request_signing.supported: false`. |

Capability-gated rows (`deterministic-testing`, `signed-requests`) are skipped only when the agent advertises the capability as `false`; they cannot be claimed and partially implemented. Declaring `supported: true` and failing the storyboard is non-conformant — declare `false` rather than ship a partial implementation.
Expand Down
1 change: 1 addition & 0 deletions dist/docs/3.0.1/building/conformance.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Every agent MUST pass every storyboard below.
| [`pagination_integrity_collection_lists`](https://adcontextprotocol.org/compliance/latest/universal/collection-lists-pagination-integrity) | `cursor` ↔ `has_more` invariant on paginated `list_collection_lists` responses; storyboard bootstraps three collection lists via `create_collection_list` and walks from a continuation page to a terminal page |
| [`pagination_integrity_property_lists`](https://adcontextprotocol.org/compliance/latest/universal/property-lists-pagination-integrity) | `cursor` ↔ `has_more` invariant on paginated `list_property_lists` responses; storyboard bootstraps three property lists via `create_property_list` and walks from a continuation page to a terminal page |
| [`deterministic_testing`](https://adcontextprotocol.org/compliance/latest/universal/deterministic-testing) | `comply_test_controller` state machine — skipped if `capabilities.compliance_testing.supported: false` |
| [`comply_controller_mode_gate`](https://adcontextprotocol.org/compliance/latest/universal/comply-controller-mode-gate) | `comply_test_controller` live-mode denial gate — verifies sellers return `FORBIDDEN` when a live-mode account calls the controller; skipped for agents that do not expose `comply_test_controller` |
| [`signed_requests`](https://adcontextprotocol.org/compliance/latest/universal/signed-requests) | RFC 9421 transport-layer request-signing verification — skipped if `request_signing.supported: false`. |

Agents that declare `capabilities.compliance_testing.supported: true` MUST implement the full [test controller](/dist/docs/3.0.1/building/implementation/comply-test-controller); a partial controller is non-conformant, so declare `false` rather than ship one.
Expand Down
94 changes: 94 additions & 0 deletions static/compliance/source/test-kits/acme-outdoor-live.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Acme Outdoor — Live-mode principal fixture
#
# Mirrors acme-outdoor.yaml with sandbox: false. Provides a live-mode bearer
# token for storyboards that test denial-path behavior when comply_test_controller
# is called with live-mode credentials.
#
# Conformance convention: sellers SHOULD treat any bearer matching the
# `demo-acme-outdoor-live-` prefix as a live-mode principal whose
# comply_test_controller calls must be refused with FORBIDDEN.
# The `demo-<kit>-` prefix convention matches storyboard-schema.yaml §Brand kit —
# sellers accept any token with this prefix (not the exact literal) so the
# suffix can rotate across spec versions without breaking previously-conformant agents.
#
# Used by: universal/comply-controller-mode-gate.yaml

id: acme_outdoor_live
name: "Acme Outdoor (Live Mode)"
description: "Live-mode principal fixture for comply_test_controller denial-path testing"
sandbox: false
supports_audio_formats: false

auth:
api_key: "demo-acme-outdoor-live-v1"
probe_task: list_creatives

brand:
house:
domain: "acmeoutdoor.example"
name: "Acme Outdoor"
brand_id: "acme_outdoor"
names:
- en: "Acme Outdoor"
description: "Premium outdoor gear for every adventure. From trail to summit, we make gear that performs."
industry: "retail"
keller_type: "master"
logos:
- url: "https://test-assets.adcontextprotocol.org/acme-outdoor/logo-primary.png"
orientation: "horizontal"
background: "light-bg"
variant: "primary"
width: 400
height: 100
- url: "https://test-assets.adcontextprotocol.org/acme-outdoor/logo-icon.png"
orientation: "square"
background: "transparent-bg"
variant: "icon"
width: 200
height: 200
colors:
primary: "#1B5E20"
secondary: "#FF6F00"
accent: "#FDD835"
background: "#FAFAFA"
text: "#212121"
fonts:
heading:
family: "Montserrat"
weight: 700
url: "https://fonts.googleapis.com/css2?family=Montserrat:wght@700"
body:
family: "Open Sans"
weight: 400
url: "https://fonts.googleapis.com/css2?family=Open+Sans"
tone:
voice: "Confident and adventurous, but never pretentious. We talk to people who do things, not people who buy things."
attributes:
- "active"
- "direct"
- "warm"
dos:
- "Use action verbs"
- "Reference real outdoor activities"
- "Keep it short"
donts:
- "Use superlatives without evidence"
- "Talk down to the reader"
- "Use corporate jargon"

assets:
images:
- id: "hero_300x250"
url: "https://test-assets.adcontextprotocol.org/acme-outdoor/hero-300x250.jpg"
width: 300
height: 250
mime_type: "image/jpeg"
description: "Medium rectangle hero — hiker on mountain trail"
text:
headlines:
- "Built for the Trail"
- "Adventure Starts Here"
cta:
- "Shop Now"
- "Explore the Collection"
click_url: "https://acmeoutdoor.example/summer-sale"
116 changes: 116 additions & 0 deletions static/compliance/source/universal/comply-controller-mode-gate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
id: comply_controller_mode_gate
version: '1.0.0'
title: 'comply_test_controller live-mode denial gate'
category: core
summary: 'Verifies that a seller refuses comply_test_controller dispatch when the resolved account is in live mode, closing the fail-open gap where every existing storyboard uses sandbox-only principals.'
track: core

required_tools:
- comply_test_controller

requires:
- controller

platform_types:
- display_ad_server
- video_ad_server
- social_platform
- retail_media
- search_platform
- audio_platform
- linear_tv_platform
- dsp
- pmax_platform
- ai_ad_network
- ai_platform
- generative_dsp

narrative: |
comply_test_controller is a sandbox-only tool — sellers MUST NOT expose it in
production. This storyboard tests that a seller's mode gate correctly refuses
comply_test_controller calls from live-mode authenticated accounts.

When a buyer agent's credentials resolve to a live-mode account, the seller
must return FORBIDDEN before executing any scenario. A gate that fails open
silently passes all other storyboards (because every other storyboard uses
sandbox principals) while allowing live-mode scenario execution.

This storyboard is automatically skipped for sellers that do not expose
comply_test_controller (requirement_unmet via requires: [controller]).

agent:
interaction_model: media_buy_seller
capabilities:
- sells_media
- supports_test_controller
examples:
- 'Any seller with comply_test_controller implementing per-account mode gating'

caller:
role: buyer_agent
example: 'Comply test harness (live-mode principal)'

prerequisites:
description: |
The seller must expose comply_test_controller and implement per-account
mode gating. The test harness authenticates with live-mode credentials
(sandbox: false) and verifies the seller refuses scenario dispatch.
test_kit: 'test-kits/acme-outdoor-live.yaml'

phases:
- id: live_account_denial
title: 'Live-mode account denial'
narrative: |
A buyer authenticated as a live-mode account calls comply_test_controller.
The seller must resolve the account mode from its own records — not the
caller's assertion — and return FORBIDDEN before executing the scenario.

steps:
- id: deny_live_caller
title: 'comply_test_controller refuses live-mode account'
requires_tool: comply_test_controller
narrative: |
Sends comply_test_controller with a well-formed force_creative_status
scenario and live-mode authentication credentials. The seller resolves
the account mode from auth, finds it is live mode, and must return
FORBIDDEN — the mode gate must fire before scenario execution.
task: comply_test_controller
comply_scenario: live_account_denial
stateful: false
expect_error: true
negative_path: payload_well_formed
expected: |
Return a ControllerError response with:
- success: false
- error: FORBIDDEN

sample_request:
scenario: force_creative_status
params:
creative_id: cr-live-probe-001
status: approved

context:
correlation_id: "comply_controller_mode_gate--deny_live_caller"
validations:
- check: field_value
path: success
allowed_values:
- false
description: 'Live-mode caller is refused'
# ControllerError.error — not adcp_error.code; ControllerError is the response
# body shape in comply-test-controller-response.json; check: error_code does not apply.
- check: field_value
path: error
allowed_values:
- FORBIDDEN
description: 'Error code is FORBIDDEN (ControllerError shape)'

- check: field_present
path: context
description: 'Response echoes back the context object'
- check: field_value
path: context.correlation_id
allowed_values:
- "comply_controller_mode_gate--deny_live_caller"
description: 'Context correlation_id returned unchanged'
Loading