From 6c196145026dcb44b4bd8267b6a976bb1572fac2 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 12:21:00 -0500 Subject: [PATCH 01/19] Add Property Governance Protocol for AdCP 3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This introduces a stateful property list management model for governance agents: ## Key Concepts - **Property Lists**: Managed resources combining base property sets with dynamic filters - **Feature Discovery**: Agents advertise scoring capabilities via `list_property_features` - **Brand Manifests**: Buyers describe their brand identity; agents apply appropriate rules - **Discriminated Unions**: `base_properties` uses `selection_type` discriminator for type safety ## Design Principles - **Scores are internal**: Governance agents filter properties; raw scores never leave the agent - **Setup-time, not bid-time**: Lists resolved during campaign planning, cached for real-time - **Identifiers-only responses**: `get_property_list` returns identifiers, not full property objects - **Notification webhooks**: Webhooks provide summary; recipients call `get_property_list` to refresh ## Schemas Added - Property list CRUD operations (create, update, get, list, delete) - Property list filters with explicit logic (`countries_all`, `channels_any`, `feature_requirements`) - Feature requirements for quantitative (`min_value`/`max_value`) and categorical (`allowed_values`) - Base property source with discriminated union (publisher_tags, publisher_ids, identifiers) - Webhook notification schema - Core identifier schema for reuse ## Documentation - Governance Protocol overview - Property Governance specification - Task reference for all CRUD operations - Brand Standards placeholder 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs.json | 35 +- docs/governance/brand-standards/index.mdx | 90 +++ docs/governance/index.mdx | 157 +++++ .../property}/adagents.mdx | 4 +- docs/governance/property/index.mdx | 227 ++++++ docs/governance/property/specification.mdx | 578 +++++++++++++++ docs/governance/property/tasks/index.mdx | 143 ++++ .../property/tasks/list_property_features.mdx | 235 +++++++ .../property/tasks/property_lists.mdx | 665 ++++++++++++++++++ .../authorized-properties.mdx | 4 +- docs/media-buy/capability-discovery/index.mdx | 4 +- .../list_authorized_properties.mdx | 2 +- docs/reference/implementor-faq.mdx | 2 +- docs/reference/roadmap.mdx | 4 +- static/schemas/source/core/identifier.json | 19 + .../source/core/property-list-ref.json | 25 + static/schemas/source/enums/adcp-domain.json | 8 +- static/schemas/source/enums/task-type.json | 18 +- static/schemas/source/index.json | 111 ++- .../source/property/base-property-source.json | 87 +++ .../create-property-list-request.json | 40 ++ .../create-property-list-response.json | 18 + .../delete-property-list-request.json | 21 + .../delete-property-list-response.json | 22 + .../source/property/feature-requirement.json | 28 + .../property/get-property-list-request.json | 35 + .../property/get-property-list-response.json | 62 ++ .../list-property-features-request.json | 27 + .../list-property-features-response.json | 21 + .../property/list-property-lists-request.json | 34 + .../list-property-lists-response.json | 44 ++ .../source/property/property-error.json | 31 + .../property/property-feature-definition.json | 75 ++ .../source/property/property-feature.json | 23 + .../property-list-changed-webhook.json | 56 ++ .../property/property-list-filters.json | 46 ++ .../source/property/property-list.json | 67 ++ .../update-property-list-request.json | 49 ++ .../update-property-list-response.json | 18 + 39 files changed, 3112 insertions(+), 23 deletions(-) create mode 100644 docs/governance/brand-standards/index.mdx create mode 100644 docs/governance/index.mdx rename docs/{media-buy/capability-discovery => governance/property}/adagents.mdx (98%) create mode 100644 docs/governance/property/index.mdx create mode 100644 docs/governance/property/specification.mdx create mode 100644 docs/governance/property/tasks/index.mdx create mode 100644 docs/governance/property/tasks/list_property_features.mdx create mode 100644 docs/governance/property/tasks/property_lists.mdx create mode 100644 static/schemas/source/core/identifier.json create mode 100644 static/schemas/source/core/property-list-ref.json create mode 100644 static/schemas/source/property/base-property-source.json create mode 100644 static/schemas/source/property/create-property-list-request.json create mode 100644 static/schemas/source/property/create-property-list-response.json create mode 100644 static/schemas/source/property/delete-property-list-request.json create mode 100644 static/schemas/source/property/delete-property-list-response.json create mode 100644 static/schemas/source/property/feature-requirement.json create mode 100644 static/schemas/source/property/get-property-list-request.json create mode 100644 static/schemas/source/property/get-property-list-response.json create mode 100644 static/schemas/source/property/list-property-features-request.json create mode 100644 static/schemas/source/property/list-property-features-response.json create mode 100644 static/schemas/source/property/list-property-lists-request.json create mode 100644 static/schemas/source/property/list-property-lists-response.json create mode 100644 static/schemas/source/property/property-error.json create mode 100644 static/schemas/source/property/property-feature-definition.json create mode 100644 static/schemas/source/property/property-feature.json create mode 100644 static/schemas/source/property/property-list-changed-webhook.json create mode 100644 static/schemas/source/property/property-list-filters.json create mode 100644 static/schemas/source/property/property-list.json create mode 100644 static/schemas/source/property/update-property-list-request.json create mode 100644 static/schemas/source/property/update-property-list-response.json diff --git a/docs.json b/docs.json index ccfa1c22f0..2ca913be1b 100644 --- a/docs.json +++ b/docs.json @@ -80,13 +80,6 @@ "docs/protocols/envelope-examples" ] }, - { - "group": "adagents.json", - "pages": [ - "docs/media-buy/capability-discovery/adagents", - "docs/media-buy/capability-discovery/authorized-properties" - ] - }, { "group": "Signals Protocol", "pages": [ @@ -101,6 +94,34 @@ } ] }, + { + "group": "Governance Protocol [3.0]", + "pages": [ + "docs/governance/index", + { + "group": "Property Governance", + "pages": [ + "docs/governance/property/index", + "docs/governance/property/specification", + "docs/governance/property/adagents", + { + "group": "Tasks", + "pages": [ + "docs/governance/property/tasks/index", + "docs/governance/property/tasks/list_property_features", + "docs/governance/property/tasks/property_lists" + ] + } + ] + }, + { + "group": "Brand Standards", + "pages": [ + "docs/governance/brand-standards/index" + ] + } + ] + }, { "group": "Curation Protocol", "pages": [ diff --git a/docs/governance/brand-standards/index.mdx b/docs/governance/brand-standards/index.mdx new file mode 100644 index 0000000000..15a738adc0 --- /dev/null +++ b/docs/governance/brand-standards/index.mdx @@ -0,0 +1,90 @@ +--- +sidebar_position: 1 +title: Brand Standards +--- + + +**AdCP 3.0 Proposal** - This protocol is under development for AdCP 3.0. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). + + +Brand Standards governance enables buyers to declare brand requirements that governance agents use to automatically apply appropriate rules across advertising workflows. + +## Overview + +Brand Standards addresses three concerns: + +| Concern | Description | Mechanism | +|---------|-------------|-----------| +| **Brand Identity** | Who is the advertiser? | Brand manifest declarations | +| **Creative Guidelines** | What can the brand show? | Creative constraints and policies | +| **Audience Constraints** | Who should see the ads? | Industry-specific targeting rules | + +## Brand Manifest + +The brand manifest is the core declaration that buyers provide to governance agents: + +```json +{ + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13", + "prohibited_categories": ["gambling", "alcohol", "tobacco"], + "creative_guidelines": { + "max_animation_duration": 15, + "require_accessibility": true + } + } +} +``` + +When governance agents receive a brand manifest, they automatically apply: + +- **Regulatory compliance**: COPPA for children's brands, GDPR consent requirements +- **Content filtering**: Block categories inappropriate for the brand's audience +- **Creative validation**: Enforce brand-specific creative guidelines + +## Integration with Property Governance + +Brand manifests work alongside property lists. When creating a property list, include the brand manifest to get automatic rule application: + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "ToyBrand Q1 Properties", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "kidsmedia.com", + "tags": ["kids_family", "educational"] + } + ], + "filters": { + "countries_all": ["US"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "brand_safety", "min_value": 90, "max_value": 100 } + ] + }, + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13" + } + } +} +``` + +The governance agent uses the brand manifest to apply COPPA requirements, filter out non-child-safe properties, and enforce relevant industry rules. + +## Coming Soon + +Detailed specification and task definitions for Brand Standards governance are under development. Key planned features: + +- **Brand manifest validation**: Verify manifest completeness and consistency +- **Industry rule sets**: Pre-defined rules for common industries (toys, alcohol, pharmaceuticals, finance) +- **Creative compliance**: Validate creatives against brand guidelines +- **Audience verification**: Ensure targeting complies with brand audience constraints + +See the [Governance Protocol overview](/docs/governance/index) for the broader context of how Brand Standards fits into AdCP governance. diff --git a/docs/governance/index.mdx b/docs/governance/index.mdx new file mode 100644 index 0000000000..53137e39e6 --- /dev/null +++ b/docs/governance/index.mdx @@ -0,0 +1,157 @@ +--- +sidebar_position: 1 +title: Governance Protocol +--- + + +**AdCP 3.0 Proposal** - This protocol is under development for AdCP 3.0. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). + + +The Governance Protocol provides standardized mechanisms for compliance, brand safety, and quality control across advertising workflows. It enables specialized agents to evaluate, filter, and score advertising entities against configurable criteria. + +## Protocol Domains + +The Governance Protocol covers four distinct governance domains: + +| Domain | What It Governs | Key Mechanisms | +|--------|-----------------|----------------| +| **[Property Governance](/docs/governance/property/index)** | Where ads can run | Property lists, compliance filtering, adagents.json authorization | +| **[Brand Standards](/docs/governance/brand-standards/index)** | Brand requirements | Brand manifests, creative guidelines, audience constraints | +| **Creative Governance** | What creatives are compliant | Format validation, content moderation, accessibility | +| **Campaign Governance** | What can be bought | Budget controls, approval workflows, policy compliance | + +Each domain has specialized governance agents that provide data, filtering, and scoring capabilities. + +## Architecture + +Governance agents operate across the **full campaign lifecycle**: + +- **Setup time**: Configure property lists, brand requirements, and compliance rules +- **Real-time**: Influence bid-time decisions through cached rules and scoring +- **Post-bid**: Measurement, verification, and reporting on compliance + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ BUYER AGENT │ +│ - Aggregates governance from specialized agents │ +│ - Issues auth_tokens for sellers to access governance data │ +│ - Source of truth for compliant lists and brand requirements │ +└───────────────────────────┬─────────────────────────────────────────────┘ + │ create_property_list, webhooks + ┌───────────────────┼───────────────────┐ + ▼ ▼ ▼ +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ Consent Agent │ │ Scope3 Agent │ │ Brand Safety │ +│ (Compliance) │ │ │ │ Agent │ +├───────────────┤ ├───────────────┤ ├───────────────┤ +│ consent_qual │ │ carbon_score │ │ content_cat │ +│ tcf_version │ │ climate_risk │ │ brand_risk │ +│ coppa_cert │ │ green_host │ │ sentiment │ +└───────────────┘ └───────────────┘ └───────────────┘ + │ + │ get_property_list (with auth_token) + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ SELLER AGENT (DSP/SSP) │ +│ - Caches resolved property lists for bid-time decisions │ +│ - Receives updates via webhooks when lists change │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +## Common Patterns + +### Feature Discovery + +All governance agents advertise their capabilities via discovery tasks: + +```json +{ + "tool": "list_property_features", + "arguments": {} +} +``` + +Response: +```json +{ + "features": [ + { "feature_id": "consent_quality", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "coppa_certified", "type": "binary" }, + { "feature_id": "carbon_score", "type": "quantitative", "range": { "min": 0, "max": 100 } } + ] +} +``` + +### Brand Manifest + +Buyers declare their brand identity, and governance agents automatically apply appropriate rules: + +```json +{ + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13" + } +} +``` + +When a governance agent sees this manifest, it automatically applies COPPA requirements, content filtering for children's brands, and other industry-specific rules. + +### Webhook Notifications + +Governance agents notify subscribers when evaluations change: + +```json +{ + "webhook_url": "https://buyer.example.com/webhooks/list-changed", + "events": ["list_updated", "property_removed", "score_changed"] +} +``` + +## Integration with Media Buy + +The Media Buy Protocol consumes governance data at multiple stages: + +- **Product discovery**: Pass `property_list_ref` to `get_products` to filter inventory +- **Media buy creation**: Reference property lists to constrain where ads can run +- **Authorization**: adagents.json validates agent authority to sell properties + +```json +{ + "tool": "get_products", + "arguments": { + "brief": "UK video inventory for Q1", + "property_list_ref": { + "agent_url": "https://buyer-agent.example.com", + "list_id": "pl_q1_uk_premium", + "auth_token": "..." + } + } +} +``` + +## Getting Started + +**Buyers:** +1. Subscribe to governance agents for property data and brand safety +2. Create property lists with filters and brand manifests +3. Aggregate results into final compliant lists +4. Share references with sellers (with auth tokens) + +**Governance Agent Implementers:** +1. Implement feature discovery tasks (`list_property_features`, etc.) +2. Implement stateful list management (CRUD operations) +3. Support webhooks to notify when evaluations change +4. See the [Property Protocol Specification](/docs/governance/property/specification) for detailed implementation guidance + +## Protocol Sections + + + + Control where ads can run with property lists, compliance filtering, and publisher authorization via adagents.json. + + + Define brand requirements including creative guidelines, audience constraints, and industry-specific rules. + + diff --git a/docs/media-buy/capability-discovery/adagents.mdx b/docs/governance/property/adagents.mdx similarity index 98% rename from docs/media-buy/capability-discovery/adagents.mdx rename to docs/governance/property/adagents.mdx index 442dee1805..2732d1d8f1 100644 --- a/docs/media-buy/capability-discovery/adagents.mdx +++ b/docs/governance/property/adagents.mdx @@ -1,9 +1,9 @@ --- -sidebar_position: 8 +sidebar_position: 3 title: adagents.json Tech Spec --- -The `adagents.json` file provides a standardized way for publishers to declare which sales agents are authorized to sell their advertising inventory. This specification addresses authorization transparency and helps prevent unauthorized reselling of publisher inventory. +The `adagents.json` file provides a standardized way for publishers to declare their properties and authorize sales agents. This is the foundation of Property Governance - it defines what properties exist and who can sell them. **[AdAgents.json Builder](https://adcontextprotocol.org/adagents)** - Validate existing files or create new ones with guided validation diff --git a/docs/governance/property/index.mdx b/docs/governance/property/index.mdx new file mode 100644 index 0000000000..d4f8468777 --- /dev/null +++ b/docs/governance/property/index.mdx @@ -0,0 +1,227 @@ +--- +sidebar_position: 1 +title: Overview +--- + + +**AdCP 3.0 Proposal** - This protocol is under development for AdCP 3.0. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). + + +Property Governance standardizes how advertising properties (websites, apps, CTV, podcasts, billboards) are identified, authorized, enriched with data, and selected for campaigns. + +## Overview + +Property Governance addresses four distinct concerns: + +| Concern | Question | Owner | Mechanism | +|---------|----------|-------|-----------| +| **Property Identity** | What properties exist? | Publishers | `adagents.json` properties array | +| **Sales Authorization** | Who can sell this property? | Publishers | `adagents.json` authorized_agents | +| **Property Data** | What do we know about this property? | Data providers | Governance agents via `list_property_features` | +| **Property Selection** | Which properties meet my requirements? | Buyers | Property lists with filters | + +The first two are **publisher-side declarations** via adagents.json. The last two are **buyer-side operations** that consume property data from governance agents. + +## Publisher Side: adagents.json + +Publishers declare their properties and authorize sales agents via `/.well-known/adagents.json`: + +```json +{ + "$schema": "https://adcontextprotocol.org/schemas/v2/adagents.json", + "properties": [ + { + "property_id": "example_site", + "property_type": "website", + "name": "Example Site", + "identifiers": [{"type": "domain", "value": "example.com"}] + } + ], + "authorized_agents": [ + { + "url": "https://agent.example.com", + "authorized_for": "Official sales agent", + "authorization_type": "property_ids", + "property_ids": ["example_site"] + } + ] +} +``` + +See the [adagents.json Tech Spec](/docs/governance/property/adagents) for complete documentation. + +## Buyer Side: Property Data and Selection + +### Property Data Providers + +Governance agents provide data about properties - compliance scores, brand safety ratings, sustainability metrics, etc. They advertise their capabilities via `list_property_features`: + +```json +{ + "features": [ + { "feature_id": "consent_quality", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "coppa_certified", "type": "binary" }, + { "feature_id": "carbon_score", "type": "quantitative", "range": { "min": 0, "max": 100 } } + ] +} +``` + +Buyers send property lists to these agents, and the agents filter and score the properties based on their specialized data. Different agents specialize in different data: + +- **Compliance vendors** (data integrity, consent quality) +- **Brand safety providers** (content classification, risk scoring) +- **Quality measurement** (viewability, fraud detection) +- **Sustainability providers** (carbon scoring, green hosting) + +### Property Selection via Governance Agents + +Buyers create **property lists on governance agents** - the agents manage these lists and apply their filtering logic: + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "Q1 Campaign - UK Premium", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } + ] + }, + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13" + } + } +} +``` + +When you provide a brand manifest, governance agents automatically apply appropriate rules (COPPA for children's brands, content filtering based on industry, etc.). + +A buyer agent typically works with **multiple governance agents** (consent, brand safety, sustainability) and aggregates/intersects their results into a final compliant list. + +## How It Fits Together + +```mermaid +flowchart TB + subgraph Buyer["BUYER AGENT"] + B1[Source of truth for compliant list] + B2[Aggregates results from specialized agents] + B3[Issues auth_tokens for sellers] + end + + subgraph Governance["GOVERNANCE AGENTS"] + CA["Consent Agent
consent_quality
tcf_version
coppa_certified"] + S3["Scope3 Agent
carbon_score
climate_risk
green_hosting"] + BS["Brand Safety Agent
content_category
brand_risk
sentiment"] + end + + subgraph Seller["SELLER AGENT (DSP/SSP)"] + SE1[Caches resolved property lists] + SE2[Uses cached lists for bid-time decisions] + end + + Buyer -->|create_property_list + webhooks| CA + Buyer -->|create_property_list + webhooks| S3 + Buyer -->|create_property_list + webhooks| BS + + Buyer -->|get_property_list with auth_token| Seller +``` + +## Sharing Property Lists with Sellers + +Once a buyer has a compliant property list, they share it with sellers: + +1. **Get a list reference**: The buyer agent exposes the list via `get_property_list` +2. **Issue an auth token**: The buyer generates a token that authorizes access to the list +3. **Pass to seller**: Include `property_list_ref` with `auth_token` in product discovery or media buy requests +4. **Seller caches locally**: Sellers fetch and cache the resolved list for bid-time decisions +5. **Webhooks for updates**: When the list changes, sellers are notified to refresh their cache + +```json +{ + "property_list_ref": { + "agent_url": "https://buyer-agent.example.com", + "list_id": "pl_q1_uk_premium", + "auth_token": "eyJhbGciOiJIUzI1NiIs..." + } +} +``` + +Sellers use this reference in `get_products` to filter available inventory: + +```json +{ + "tool": "get_products", + "arguments": { + "brief": "UK video inventory for Q1", + "property_list_ref": { + "agent_url": "https://buyer-agent.example.com", + "list_id": "pl_q1_uk_premium", + "auth_token": "..." + } + } +} +``` + +## Relationship to Other Protocols + +### Property Governance + Media Buy + +The Media Buy Protocol consumes property lists at multiple stages: + +- **Product discovery**: Pass `property_list_ref` to `get_products` to filter inventory to compliant properties +- **Media buy creation**: Reference property lists to constrain where ads can run +- **Authorization**: adagents.json validates agent authority to sell + +### Property Governance + Signals + +Both protocols operate on properties but serve different purposes: + +| Signals Protocol | Property Governance | +|------------------|---------------------| +| Audience/contextual data | Property metadata and compliance | +| "Who should see this ad?" | "Where can this ad run?" | +| Signal activation | Property filtering | + +## Tasks + +### Discovery + +- **[list_property_features](/docs/governance/property/tasks/list_property_features)**: Discover what data a governance agent can provide about properties + +### Property List Management + +- **[create_property_list](/docs/governance/property/tasks/property_lists#create_property_list)**: Create a new property list on a governance agent +- **[get_property_list](/docs/governance/property/tasks/property_lists#get_property_list)**: Retrieve resolved properties (with caching guidance) +- **[update_property_list](/docs/governance/property/tasks/property_lists#update_property_list)**: Modify filters or base properties +- **[delete_property_list](/docs/governance/property/tasks/property_lists#delete_property_list)**: Remove a property list + +## Getting Started + +**Publishers:** +1. Create `/.well-known/adagents.json` with property definitions +2. Authorize sales agents for your properties + +**Buyers:** +1. Subscribe to governance agents for property data +2. Create property lists on each governance agent with filters and brand manifests +3. Aggregate results into a final compliant list +4. Share property list references with sellers (with auth tokens) + +**Governance Agent Implementers:** +1. Implement `list_property_features` to advertise your capabilities +2. Implement property list CRUD operations +3. Support webhooks to notify buyers when evaluations change +4. See the [Protocol Specification](/docs/governance/property/specification) for implementation details + +See the [Protocol Specification](/docs/governance/property/specification) for detailed implementation guidance. diff --git a/docs/governance/property/specification.mdx b/docs/governance/property/specification.mdx new file mode 100644 index 0000000000..a000a3a221 --- /dev/null +++ b/docs/governance/property/specification.mdx @@ -0,0 +1,578 @@ +--- +sidebar_position: 2 +title: Protocol Specification +--- + + +**AdCP 3.0 Proposal** - This specification is under development for AdCP 3.0. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). + + +**Status**: Request for Comments +**Last Updated**: January 2026 + +## Abstract + +The Property Protocol defines a standard Model Context Protocol (MCP) and Agent-to-Agent (A2A) interface for property identity, authorization, data provision, and selection. This protocol enables publishers to declare properties and authorized agents, data providers to offer property intelligence, and buyers to select compliant property sets. + +## Overview + +The Property Protocol addresses four distinct concerns: + +| Concern | Question | Owner | Mechanism | +|---------|----------|-------|-----------| +| **Property Identity** | What properties exist? | Publishers | `adagents.json` properties array | +| **Sales Authorization** | Who can sell this property? | Publishers | `adagents.json` authorized_agents | +| **Property Data** | What do we know about this property? | Data providers | Governance agents via `list_property_features` | +| **Property Selection** | Which properties meet my requirements? | Buyers | Property lists with filters | + +The first two are **publisher-side declarations** via adagents.json. The last two are **buyer-side operations** that consume property data from governance agents. + +### Property Data and Selection + +Property data and selection use a **stateful** model: + +- **Feature discovery**: Agents advertise what they can evaluate via `list_property_features` +- **Property list management**: CRUD operations for managed property lists with filters +- **Brand manifests**: Let agents automatically apply rules based on brand characteristics +- **Webhook notifications**: Real-time updates when resolved lists change +- **Marketplace architecture**: Multiple specialized agents as subscription services + +All evaluation (scoring, filtering, discovery) happens implicitly when property lists are resolved via `get_property_list`. + +## Core Concepts + +### Request Roles and Relationships + +Every governance request involves two key roles: + +#### Orchestrator +The platform or system making the API request to the governance agent: +- **Examples**: DSP, trading desk platform, campaign management tool +- **Responsibilities**: Makes API calls, handles authentication, manages the technical interaction +- **Account**: Has technical credentials and API access to the governance agent + +#### Principal +The entity on whose behalf the request is being made: +- **Examples**: Advertiser (Nike), agency (Omnicom), brand team +- **Responsibilities**: Owns the campaign objectives and policy requirements +- **Policies**: May have custom thresholds, blocklists, or compliance requirements + +### Property Identification + +Properties are identified using the standard AdCP property model: + +```json +{ + "property_type": "website", + "name": "Example News", + "identifiers": [ + { "type": "domain", "value": "example.com" } + ] +} +``` + +Property types include: `website`, `mobile_app`, `ctv_app`, `dooh`, `podcast`, `radio`, `streaming_audio`. + +### Property List References + +For large property sets, use property list references instead of embedding properties: + +```json +{ + "property_list_ref": { + "agent_url": "https://lists.example.com", + "list_id": "premium_news_sites", + "auth_token": "eyJhbGciOiJIUzI1NiIs..." + } +} +``` + +The receiving agent fetches and caches the list independently, enabling: +- **Scale**: Pass 50,000+ properties without payload bloat +- **Updates**: Lists evolve without changing requests +- **Authorization**: Token controls access to the list + +### Governance Agent Types + +#### Compliance Agents +Specialized vendors providing property compliance intelligence: +- **Examples**: Data integrity scoring, consent quality measurement +- **Business Model**: Subscription or per-query pricing +- **Methodology**: Published rubrics for transparency + +#### Brand Safety Agents +Content classification and risk assessment: +- **Examples**: Content categorization, brand safety scoring +- **Coverage**: May specialize by channel or geography + +#### Quality Agents +Performance and fraud measurement: +- **Examples**: Viewability prediction, IVT detection +- **Integration**: May correlate with campaign outcomes + +### Scoring and Data Privacy + +#### Scores Are Internal + +**Critical design principle**: Raw scores are NOT shared with buyers or downstream clients. This prevents data leakage. + +Governance agents maintain internal scoring models, but the protocol is designed around **list management**, not score exposure: + +- Buyers specify **thresholds** via `feature_requirements` (e.g., `"min_value": 85`) +- Agents return **pass/fail lists** of properties that meet the thresholds +- Raw scores never leave the governance agent + +This design prevents: +- Score enumeration attacks (running lists with different thresholds to reverse-engineer scores) +- Competitive intelligence leakage +- Data arbitrage where buyers resell scoring data + +#### What Buyers Receive + +When calling `get_property_list`, buyers receive a compact list of identifiers (not full property objects) for efficiency: + +```json +{ + "list_id": "pl_abc123", + "identifiers": [ + { "type": "domain", "value": "bbc.co.uk" }, + { "type": "domain", "value": "theguardian.com" }, + { "type": "domain", "value": "ft.com" } + ], + "total_count": 847 +} +``` + +Properties that pass the threshold are included. Properties that fail are excluded. No scores or property metadata are returned - just the identifiers needed for bid-time lookups. + +#### Methodology Discovery + +The `list_property_features` task returns information about what features an agent evaluates and their methodology, but NOT the underlying scores: + +```json +{ + "features": [ + { + "feature_id": "consent_quality", + "name": "Consent Quality Score", + "type": "quantitative", + "range": { "min": 0, "max": 100 }, + "methodology": "data_integrity_index", + "methodology_version": "v2.1", + "methodology_url": "https://compliance.example.com/methodology" + } + ] +} +``` + +This allows buyers to: +- Understand what an agent measures +- Compare methodologies across agents +- Set appropriate thresholds + +But they cannot retrieve the actual scores for individual properties. + +## Tasks + +### Discovery + +#### list_property_features + +Discover what features a governance agent can evaluate. + +**Use Cases**: +- Capability discovery: Understand what an agent can evaluate +- Marketplace browsing: Compare features across agents +- Integration planning: Know what filters are available before creating lists + +### Property List Management + +#### create_property_list + +Create a new property list with filters and optional brand manifest. + +**Required Parameters**: +- At least one country in `countries_all` (ISO country code) +- At least one channel in `channels_any` (display, video, audio, etc.) + +**Base Properties**: An array of property sources to evaluate. Each entry is a discriminated union with `selection_type` as the discriminator: +- **`publisher_tags`**: `{ "selection_type": "publisher_tags", "publisher_domain": "...", "tags": [...] }` - tags scoped to publisher +- **`publisher_ids`**: `{ "selection_type": "publisher_ids", "publisher_domain": "...", "property_ids": [...] }` - property IDs scoped to publisher +- **`identifiers`**: `{ "selection_type": "identifiers", "identifiers": [...] }` - no publisher context needed +- **Omitted**: Query the agent's entire property database + +See the [base-property-source schema](https://adcontextprotocol.org/schemas/v2/property/base-property-source.json) for the full specification. + +**Filter Logic** (explicit in field names): +- `countries_all`: Property must be compliant in **ALL** listed countries - this is critical because different jurisdictions have different laws +- `channels_any`: Property must support **ANY** of the listed channels +- `feature_requirements`: Property must pass **ALL** requirements (AND) + +**Use Cases**: +- Define compliant property sets with filters (country, channel, feature thresholds) +- Provide brand manifest for automatic rule application +- Register webhook URL for change notifications + +#### update_property_list + +Modify an existing property list. + +**Use Cases**: +- Add or remove properties from base list +- Adjust filters based on campaign needs +- Update webhook URL + +#### get_property_list + +Retrieve a property list with resolved properties. + +**Use Cases**: +- Get the current list of compliant properties after filters applied +- Cache resolved list for bid-time use +- Retrieve updated list after webhook notification + +#### list_property_lists + +List all property lists accessible to the authenticated principal. + +#### delete_property_list + +Remove a property list. + +## Typical Flows + +### Property List Flow + +Property lists enable buyers to define and manage compliant property sets: + +1. **Create property list**: Buyer defines list on governance agent with filters +2. **Resolve and iterate**: Buyer calls `get_property_list` to see resolved properties +3. **Share list reference**: Buyer provides `list_id` to orchestrator/seller +4. **Cache locally**: Orchestrator/seller fetches and caches resolved properties +5. **Use at bid time**: Orchestrator/seller uses local cache (no governance agent calls) +6. **Refresh periodically**: Re-fetch based on `cache_valid_until` (typically 1-24 hours) + +**Important**: Governance agents are NOT in the real-time bid path. All bid-time decisions use locally cached property sets. + +### Webhook and Caching Pattern + +Webhooks provide **notification** that a property list has changed. The webhook payload contains a summary of changes, but you must call `get_property_list` to retrieve the actual updated properties. + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Webhook Flow │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Governance agent re-evaluates properties (background) │ +│ 2. Webhook fires with change summary (added/removed counts) │ +│ 3. Recipient calls get_property_list to fetch updated list │ +│ 4. Recipient updates local cache │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +**Best Practices for Downstream Consumers**: + +Consumers of property lists (orchestrators, sellers, buyer agents) should implement **at least one** of these patterns: + +1. **Webhook-driven updates** (recommended): Register a webhook URL when creating the property list. Re-fetch via `get_property_list` when notified of changes. + +2. **Polling with cache hints**: Use `cache_valid_until` from `get_property_list` responses to schedule periodic re-fetches. Typical validity periods are 1-24 hours. + +3. **Hybrid approach**: Use webhooks for immediate updates, with polling as a fallback safety net. + +**Cache Expiry Guidance**: + +Every `get_property_list` response includes: +- `resolved_at`: When the list was evaluated +- `cache_valid_until`: When consumers should consider the cache stale + +```json +{ + "resolved_at": "2026-01-04T10:00:00Z", + "cache_valid_until": "2026-01-04T22:00:00Z" +} +``` + +Consumers MUST NOT use cached data beyond `cache_valid_until` without re-fetching. + +### Property Discovery Flow + +1. **Define filters**: Specify country, channel, quality thresholds when creating property list +2. **Resolve list**: Call `get_property_list` with `resolve=true` to get matching properties +3. **Review candidates**: Evaluate returned properties for fit +4. **Add to campaign**: Include property list reference in media buy + +## Response Structure + +All AdCP Governance responses follow a consistent structure: + +### Core Response Fields +- **message**: Human-readable summary of the operation result +- **context_id**: Session continuity identifier for follow-up requests +- **data**: Task-specific payload (varies by task) + +### Protocol Transport +- **MCP**: Returns complete response as flat JSON object +- **A2A**: Returns as structured artifacts with message in text part, data in data part +- **Data Consistency**: Both protocols contain identical AdCP data structures + +## Error Handling + +### Error Codes + +- `PROPERTY_NOT_FOUND`: Property identifier not recognized +- `PROPERTY_NOT_MONITORED`: Governance agent doesn't cover this property +- `POLICY_NOT_FOUND`: Referenced policy doesn't exist +- `LIST_ACCESS_DENIED`: Cannot access property list (auth failed) +- `LIST_NOT_FOUND`: Property list reference invalid +- `METHODOLOGY_NOT_SUPPORTED`: Requested methodology version unavailable +- `PARTIAL_RESULTS`: Some properties couldn't be evaluated + +### Partial Success + +For bulk operations, the response may include partial results: + +```json +{ + "message": "Evaluated 847 of 850 properties. 3 properties not in coverage.", + "context_id": "ctx-gov-123", + "scores": [...], + "errors": [ + { + "code": "PROPERTY_NOT_MONITORED", + "property": { "identifiers": [{ "type": "domain", "value": "unknown.com" }] }, + "message": "Property not in monitoring coverage" + } + ] +} +``` + +## Implementation Notes + +### Caching Architecture + +Governance decisions are highly cacheable: + +#### Orchestrator-Side Caching +- **Score cache**: Store scores with TTL from `valid_until` field +- **Decision cache**: Pre-compute pass/fail for campaigns +- **List cache**: Cache property lists from `property_list_ref` + +#### Agent-Side Caching +- **Profile cache**: Maintain pre-computed property profiles +- **Methodology cache**: Cache scoring algorithm results + +### Performance Requirements + +| Operation | Target Latency | +|-----------|----------------| +| Single property score | < 100ms | +| Bulk scoring (100 properties) | < 2s | +| Filter decision (cached) | < 5ms | +| Property discovery | < 5s | + +### Multi-Agent Strategies + +Orchestrators may consult multiple governance agents: + +1. **Primary + Validation**: Use primary agent, validate with secondary +2. **Specialization**: Route by property type to specialist agents +3. **Consensus**: Require multiple agents to agree +4. **Competitive**: Track agent accuracy, weight by performance + +## Agent Discovery + +Governance agents expose capabilities via `.well-known/agent-card.json`: + +```json +{ + "name": "Example Compliance Provider", + "url": "https://compliance.example.com", + "capabilities": { + "tasks": [ + "list_property_features", + "create_property_list", + "get_property_list", + "update_property_list", + "delete_property_list", + "list_property_lists" + ], + "protocols": ["MCP", "A2A"], + "schema_version": "v1" + }, + "methodology": { + "documentation_url": "https://compliance.example.com/methodology", + "scoring_frameworks": ["data_integrity_index", "brand_safety_score"], + "coverage": { + "property_types": ["website", "mobile_app", "ctv_app"], + "jurisdictions": ["GDPR", "CCPA", "COPPA"] + } + } +} +``` + +Use `list_property_features` for detailed capability discovery: + +```json +{ + "tool": "list_property_features", + "arguments": {} +} +``` + +Returns the specific features the agent can evaluate (consent_quality, carbon_score, brand_risk, etc.). + +## Marketplace Architecture + +The Property Protocol enables a marketplace of specialized data agents: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ SELLER AGENT (DSP/SSP) │ +│ "Give me the compliant property list for this campaign" │ +└───────────────────────────────┬─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ BUYER AGENT (implements Property Protocol) │ +│ - Exposes: list_property_features, get_property_list, webhooks │ +│ - Source of truth for final compliant list │ +│ - Intersects results from specialized agents │ +└───────────────────────────┬─────────────────────────────────────────────┘ + │ + ┌───────────────────┼───────────────────┐ + ▼ ▼ ▼ +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ Consent Agent │ │ Scope3 Agent │ │ Brand Safety │ +│ (Compliant) │ │ │ │ Agent │ +├───────────────┤ ├───────────────┤ ├───────────────┤ +│ Features: │ │ Features: │ │ Features: │ +│ consent_qual │ │ carbon_score │ │ content_cat │ +│ tcf_version │ │ climate_risk │ │ brand_risk │ +│ coppa_cert │ │ green_host │ │ sentiment │ +├───────────────┤ ├───────────────┤ ├───────────────┤ +│ Subscription │ │ Subscription │ │ Subscription │ +└───────────────┘ └───────────────┘ └───────────────┘ +``` + +### Key Principles + +1. **Buyer agent is source of truth**: The buyer agent aggregates data from multiple specialized governance agents +2. **Seller sees one interface**: Sellers interact only with the buyer agent using standard Property Protocol +3. **Subscription model**: Each specialized agent is a paid service with its own features and coverage +4. **Webhook-driven updates**: Specialized agents notify the buyer agent when property evaluations change + +### Multi-Agent Orchestration + +A buyer agent can distribute a master property list to multiple specialized agents: + +```python +# Buyer agent creates variants on each specialized agent +consent_list = consent_agent.create_property_list( + name="Q1 Campaign - Consent", + base_properties=master_list, + brand_manifest=brand_manifest +) +# Configure webhook for updates +consent_agent.update_property_list( + list_id=consent_list.list_id, + webhook_url="https://buyer.example.com/webhooks/consent" +) + +scope3_list = scope3_agent.create_property_list( + name="Q1 Campaign - Sustainability", + base_properties=master_list, + brand_manifest=brand_manifest +) +scope3_agent.update_property_list( + list_id=scope3_list.list_id, + webhook_url="https://buyer.example.com/webhooks/scope3" +) + +# Buyer agent intersects filtered results +def on_list_changed(event): + consent_props = consent_agent.get_property_list(consent_list.list_id, resolve=True) + scope3_props = scope3_agent.get_property_list(scope3_list.list_id, resolve=True) + + # Intersection = properties that pass ALL governance agents + compliant_props = intersect(consent_props, scope3_props) + + # Update buyer agent's exposed list + update_compliant_list(compliant_props) +``` + +### Brand Manifest + +Instead of specifying complex filters, buyers provide a brand manifest: + +```json +{ + "brand_manifest": { + "brand_name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13", + "content_adjacency": ["kids_family", "education"], + "excluded_content": ["violence", "adult"] + } +} +``` + +Each governance agent interprets the manifest according to their domain expertise: +- **Consent agent**: Applies COPPA requirements for children_under_13 +- **Brand safety agent**: Filters to kids_family content, excludes violence/adult +- **Sustainability agent**: Applies any green media requirements + +The buyer doesn't need to know the specific rules - they declare who they are, and agents figure out what applies. + +## Integration with Media Buy Protocol + +### Property Lists in Media Buys + +The Media Buy Protocol accepts property list references: + +```json +{ + "task": "create_media_buy", + "arguments": { + "packages": [{ + "property_list_ref": { + "agent_url": "https://governance.example.com", + "list_id": "approved_q1_campaign", + "auth_token": "..." + } + }] + } +} +``` + +### Policy Compliance + +Media buys can reference governance policies via property list references: + +```json +{ + "compliance_requirements": { + "property_list_ref": { + "agent_url": "https://compliance.example.com", + "list_id": "pl_q1_compliant", + "auth_token": "eyJhbGciOiJIUzI1NiIs..." + } + } +} +``` + +## Best Practices + +1. **Cache aggressively**: Property scores change slowly; cache for hours/days +2. **Bulk where possible**: Use batch operations for planning, not per-property calls +3. **Pre-compute decisions**: Build pass/fail lookups before bid-time +4. **Monitor coverage**: Track which properties agents don't cover +5. **Log methodology versions**: For audit trails, record which scoring version was used +6. **Handle partial results**: Not all properties will be scorable; plan for gaps + +## Next Steps + +- See the [adagents.json Tech Spec](/docs/governance/property/adagents) for property declaration and authorization +- See the [list_property_features task reference](/docs/governance/property/tasks/list_property_features) for capability discovery +- See the [Property List Management](/docs/governance/property/tasks/property_lists) for CRUD operations and webhooks diff --git a/docs/governance/property/tasks/index.mdx b/docs/governance/property/tasks/index.mdx new file mode 100644 index 0000000000..e7cdac9501 --- /dev/null +++ b/docs/governance/property/tasks/index.mdx @@ -0,0 +1,143 @@ +--- +sidebar_position: 1 +title: Task Reference +--- + +# Property Governance Tasks + + +**AdCP 3.0 Proposal** - These tasks are under development for AdCP 3.0. + + +Property governance uses a **stateful** model where all evaluation happens through property list management. Create lists with filters and brand manifests, then resolve them to get compliant properties. + +## Discovery + +| Task | Purpose | Response Time | +|------|---------|---------------| +| [list_property_features](/docs/governance/property/tasks/list_property_features) | Discover agent capabilities | ~200ms | + +## Property List Management + +| Task | Purpose | Response Time | +|------|---------|---------------| +| [create_property_list](/docs/governance/property/tasks/property_lists#create_property_list) | Create a new property list | ~500ms | +| [update_property_list](/docs/governance/property/tasks/property_lists#update_property_list) | Modify an existing list | ~500ms | +| [get_property_list](/docs/governance/property/tasks/property_lists#get_property_list) | Retrieve list with resolved properties | ~2-5s | +| [list_property_lists](/docs/governance/property/tasks/property_lists#list_property_lists) | List all property lists | ~500ms | +| [delete_property_list](/docs/governance/property/tasks/property_lists#delete_property_list) | Delete a property list | ~200ms | + +See [Property List Management](/docs/governance/property/tasks/property_lists) for complete CRUD documentation. + +## Task Selection Guide + +### Creating a Property List + +Use `create_property_list` with filters and brand manifest: + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "Q1 Campaign - UK Premium", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { + "feature_id": "consent_quality", + "min_value": 85, + "max_value": 100 + }, + { + "feature_id": "coppa_certified", + "allowed_values": [true] + } + ] + }, + "brand_manifest": { + "name": "ToyBrand", + "industry": "toys", + "target_audience": "children_under_13" + } + } +} +``` + +**Required filters**: At least one country in `countries_all` and one channel in `channels_any` must be provided. + +**Base properties**: An array of property sources to evaluate. Each entry is a discriminated union with `selection_type`: +- **`publisher_tags`**: `{ "selection_type": "publisher_tags", "publisher_domain": "...", "tags": [...] }` +- **`publisher_ids`**: `{ "selection_type": "publisher_ids", "publisher_domain": "...", "property_ids": [...] }` +- **`identifiers`**: `{ "selection_type": "identifiers", "identifiers": [...] }` +- **Omitted**: Query the agent's entire property database + +**Filter logic** (explicit in field names): +- `countries_all`: Property must be compliant in ALL listed countries (different jurisdictions have different laws) +- `channels_any`: Property must support ANY of the listed channels +- `feature_requirements`: Property must pass ALL requirements (AND) + +Filters have two built-in fields (`countries_all`, `channels_any`) plus `feature_requirements` which reference features the agent provides (discovered via `list_property_features`). For quantitative features, use `min_value`/`max_value`. For binary or categorical features, use `allowed_values`. + +### Getting Resolved Properties + +Use `get_property_list` to retrieve the list. You'll receive an `auth_token` that can be shared with sellers: + +```json +{ + "tool": "get_property_list", + "arguments": { + "list_id": "pl_abc123", + "resolve": true + } +} +``` + +Response includes an auth token for sharing. Note that **raw scores are not returned** - only properties that pass the filter thresholds are included: + +```json +{ + "list_id": "pl_abc123", + "auth_token": "eyJhbGciOiJIUzI1NiIs...", + "properties": [...], + "cache_valid_until": "2026-01-04T17:15:00Z" +} +``` + +Pass the `auth_token` to sellers so they can fetch and cache the resolved list. + +### Multi-Agent Integration + +Create the same property list on multiple governance agents, then configure webhooks to aggregate results: + +```python +# Create lists on specialized agents +consent_list = consent_agent.create_property_list( + name="Q1 - Consent", + base_properties=master_list, + brand_manifest=brand_manifest +) +consent_agent.update_property_list( + list_id=consent_list.list_id, + webhook_url="https://buyer.example.com/webhooks/consent" +) + +scope3_list = scope3_agent.create_property_list( + name="Q1 - Sustainability", + base_properties=master_list, + brand_manifest=brand_manifest +) +scope3_agent.update_property_list( + list_id=scope3_list.list_id, + webhook_url="https://buyer.example.com/webhooks/scope3" +) + +# Buyer agent intersects results when webhooks fire +``` diff --git a/docs/governance/property/tasks/list_property_features.mdx b/docs/governance/property/tasks/list_property_features.mdx new file mode 100644 index 0000000000..6c7dcd16a7 --- /dev/null +++ b/docs/governance/property/tasks/list_property_features.mdx @@ -0,0 +1,235 @@ +--- +sidebar_position: 1 +title: list_property_features +--- + + +**AdCP 3.0 Proposal** - This task is under development for AdCP 3.0. + + +**Task**: Discover what features a property governance agent can evaluate. + +**Response Time**: ~200ms + +**Request Schema**: [`https://adcontextprotocol.org/schemas/v1/governance/list-property-features-request.json`](https://adcontextprotocol.org/schemas/v1/governance/list-property-features-request.json) +**Response Schema**: [`https://adcontextprotocol.org/schemas/v1/governance/list-property-features-response.json`](https://adcontextprotocol.org/schemas/v1/governance/list-property-features-response.json) + +The `list_property_features` task returns the features a governance agent can evaluate. This is the discovery mechanism for understanding an agent's capabilities before using other governance tasks. + +## Request Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `property_types` | string[] | No | Filter to features available for these property types | +| `countries` | string[] | No | Filter to features available in these countries | + +## Response Structure + +All AdCP responses include: +- **message**: Human-readable summary of the operation result +- **context_id**: Session continuity identifier for follow-up requests +- **data**: Task-specific payload (see Response Data below) + +## Response Data + +```json +{ + "features": [ + { + "feature_id": "consent_quality", + "name": "Consent Quality Score", + "description": "Measures the quality of consent implementation including UX, granularity, and compliance", + "type": "quantitative", + "range": { "min": 0, "max": 100 }, + "coverage": { + "property_types": ["website", "mobile_app"], + "countries": [] + } + }, + { + "feature_id": "coppa_certified", + "name": "COPPA Certified", + "description": "Whether the property has COPPA certification for child-directed content", + "type": "binary", + "coverage": { + "property_types": ["website", "mobile_app", "ctv_app"], + "countries": ["US"] + } + }, + { + "feature_id": "content_category", + "name": "Content Category", + "description": "IAB content category classification", + "type": "categorical", + "allowed_values": ["news", "sports", "entertainment", "kids_family", "technology", "business", "adult", "violence"] + } + ] +} +``` + +### Feature Types + +| Type | Description | Schema Fields | +|------|-------------|---------------| +| `binary` | True/false values | None additional | +| `quantitative` | Numeric values within a range | `range: { min, max }` | +| `categorical` | Enumerated string values | `allowed_values: string[]` | + +### Coverage + +The `coverage` object indicates where a feature applies: +- **property_types**: Empty array means all property types +- **countries**: Empty array means all countries + +## Protocol-Specific Examples + +### MCP Request + +```json +{ + "tool": "list_property_features", + "arguments": {} +} +``` + +### MCP Response + +```json +{ + "message": "This agent evaluates 12 features across consent, brand safety, and sustainability.", + "context_id": "ctx-gov-features-123", + "features": [ + { + "feature_id": "consent_quality", + "name": "Consent Quality Score", + "description": "Measures the quality of consent implementation", + "type": "quantitative", + "range": { "min": 0, "max": 100 } + }, + { + "feature_id": "tcf_version", + "name": "TCF Version", + "description": "IAB Transparency & Consent Framework version supported", + "type": "categorical", + "allowed_values": ["none", "tcf_1.1", "tcf_2.0", "tcf_2.2"] + }, + { + "feature_id": "coppa_certified", + "name": "COPPA Certified", + "description": "Whether property has COPPA certification", + "type": "binary", + "coverage": { + "property_types": ["website", "mobile_app", "ctv_app"], + "countries": ["US"] + } + } + ] +} +``` + +### MCP Request - Filtered by Property Type + +```json +{ + "tool": "list_property_features", + "arguments": { + "property_types": ["mobile_app"] + } +} +``` + +### A2A Request + +```javascript +await a2a.send({ + message: { + parts: [{ + kind: "data", + data: { + skill: "list_property_features", + parameters: {} + } + }] + } +}); +``` + +## Example Feature Sets by Agent Type + +### Consent/Compliance Agent + +```json +{ + "features": [ + { "feature_id": "consent_quality", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "tcf_version", "type": "categorical", "allowed_values": ["none", "tcf_2.0", "tcf_2.2"] }, + { "feature_id": "coppa_certified", "type": "binary" }, + { "feature_id": "gpp_supported", "type": "binary" }, + { "feature_id": "vendor_count", "type": "quantitative", "range": { "min": 0, "max": 1000 } } + ] +} +``` + +### Sustainability Agent (e.g., Scope3) + +```json +{ + "features": [ + { "feature_id": "carbon_score", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "green_certified", "type": "binary" }, + { "feature_id": "renewable_hosting", "type": "binary" }, + { "feature_id": "emissions_per_impression", "type": "quantitative", "range": { "min": 0, "max": 100 } } + ] +} +``` + +### Brand Safety Agent + +```json +{ + "features": [ + { "feature_id": "content_category", "type": "categorical", "allowed_values": ["news", "sports", ...] }, + { "feature_id": "brand_risk_score", "type": "quantitative", "range": { "min": 0, "max": 100 } }, + { "feature_id": "sentiment", "type": "categorical", "allowed_values": ["positive", "neutral", "negative"] }, + { "feature_id": "made_for_advertising", "type": "binary" } + ] +} +``` + +## Integration Pattern + +Use `list_property_features` to understand agent capabilities before creating property lists: + +```python +# Step 1: Discover agent capabilities +features_response = governance_agent.list_property_features() + +# Step 2: Build feature_requirements using available features +feature_requirements = [] +for feature in features_response.features: + if feature.feature_id == "consent_quality" and feature.type == "quantitative": + feature_requirements.append({ + "feature_id": "consent_quality", + "min_value": 80, + "max_value": 100 + }) + if feature.feature_id == "coppa_certified" and feature.type == "binary": + feature_requirements.append({ + "feature_id": "coppa_certified", + "allowed_values": [True] + }) + +# Step 3: Create property list with feature requirements +governance_agent.create_property_list( + name="Q1 Campaign", + filters={"feature_requirements": feature_requirements}, + brand_manifest=my_brand_manifest +) +``` + +## Usage Notes + +1. **Call first**: Use this task to discover capabilities before other governance tasks +2. **Cache results**: Feature definitions change infrequently; cache for hours/days +3. **Check coverage**: Not all features apply to all property types or countries +4. **Marketplace discovery**: Use this to understand what different governance agents offer diff --git a/docs/governance/property/tasks/property_lists.mdx b/docs/governance/property/tasks/property_lists.mdx new file mode 100644 index 0000000000..e2f3afd191 --- /dev/null +++ b/docs/governance/property/tasks/property_lists.mdx @@ -0,0 +1,665 @@ +--- +sidebar_position: 5 +title: Property List Management +--- + + +**AdCP 3.0 Proposal** - These tasks are under development for AdCP 3.0. + + +**Tasks**: Create, update, get, list, and delete property lists. + +Property lists are managed resources that combine static property sets with dynamic filters. When resolved, filters are applied to produce the final property set. + +## Architecture: Setup Time, Not Real-Time + +Property lists are designed for **setup-time** operations, not real-time bid decisions: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ SETUP TIME (Campaign Planning) │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Buyer creates/updates property list on governance agent │ +│ 2. Buyer resolves list to get current properties │ +│ 3. Buyer provides list_id to orchestrator/seller │ +│ 4. Orchestrator/seller fetches and caches resolved list │ +│ 5. Campaign targets only cached compliant properties │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ BID TIME (Milliseconds) │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ • Orchestrator/seller uses LOCAL cache only │ +│ • NO calls to governance agent │ +│ • Pass/fail from cached property set │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ REFRESH (Periodic) │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ • Orchestrator/seller re-fetches list on schedule │ +│ • Frequency based on cache_valid_until │ +│ • Typically every 1-24 hours │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +This enables: + +- **Static lists**: Curated sets of approved properties +- **Dynamic lists**: Properties matching criteria (country, channel, score thresholds) +- **Hybrid lists**: Base set modified by filters + +## Tasks Overview + +| Task | Purpose | Response Time | +|------|---------|---------------| +| `create_property_list` | Create a new property list | ~500ms | +| `update_property_list` | Modify an existing list | ~500ms | +| `get_property_list` | Retrieve list with resolved properties | ~2-5s (depending on size) | +| `list_property_lists` | List all property lists | ~500ms | +| `delete_property_list` | Delete a property list | ~200ms | + +## Property List Structure + +A property list contains: + +```json +{ + "list_id": "uk_premium_news_q1", + "name": "UK Premium News Sites Q1 2026", + "description": "High-quality UK news sites for Q1 campaign", + "principal": "did:principal:brand-x", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news", "uk_tier1"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 90, "max_value": 100 } + ] + }, + "brand_manifest": { + "industry": "retail", + "target_audience": "general" + }, + "created_at": "2026-01-03T10:00:00Z", + "updated_at": "2026-01-03T10:00:00Z", + "property_count": 847 +} +``` + +## Brand Manifest + +Instead of manually specifying all filters, provide a brand manifest and let the governance agent apply appropriate rules based on who you are: + +```json +{ + "brand_manifest": { + "name": "ToyBrand", + "industry": "toys", + "target_audience": "children under 13", + "tone": "playful and safe" + } +} +``` + +The agent interprets the manifest based on its domain expertise: +- A consent agent applies COPPA requirements based on `target_audience` +- A brand safety agent infers content categories from `industry` +- A sustainability agent applies requirements from the brand's profile + +The brand manifest uses the standard [core/brand-manifest](https://adcontextprotocol.org/schemas/v2/core/brand-manifest.json) schema which includes fields for `industry`, `target_audience`, `tone`, and other brand identity information. + +## Webhooks + +Configure webhooks via `update_property_list` to receive notifications when the resolved list changes. + +**Important**: Webhooks provide **notification only**. They tell you that the list has changed, but do not stream the updated properties. After receiving a webhook, you must call `get_property_list` to retrieve the updated property set. + +### Webhook Flow + +``` +1. Governance agent re-evaluates properties (periodically or on trigger) +2. Agent detects changes to the resolved property list +3. Webhook fires with change summary (counts, not full list) +4. Recipient calls get_property_list(list_id, resolve=true) +5. Recipient updates local cache with new properties +``` + +### Webhook Payload + +```json +{ + "event": "property_list_changed", + "list_id": "uk_premium_news_q1", + "list_name": "UK Premium News Sites Q1 2026", + "change_summary": { + "properties_added": 12, + "properties_removed": 3, + "total_properties": 856 + }, + "resolved_at": "2026-01-03T18:00:00Z", + "cache_valid_until": "2026-01-04T18:00:00Z" +} +``` + +The webhook payload includes counts but NOT the actual properties. This keeps payloads small and avoids redundant data transfer when recipients may not need the full list immediately. + +### Webhook Use Cases + +1. **Buyer agent aggregation**: Receive updates from multiple specialized agents, intersect results +2. **Seller cache invalidation**: Know when to re-fetch the compliant property list +3. **Alerting**: Notify when significant changes occur to compliance status + +## Filters + +Filters are applied when the list is resolved (via `get_property_list`): + +| Filter | Type | Description | +|--------|------|-------------| +| `countries_all` | string[] | ISO country codes - property must be compliant in ALL (required) | +| `channels_any` | string[] | Advertising channels - property must support ANY (required) | +| `property_types` | string[] | Property types (website, mobile_app, ctv_app, etc.) | +| `feature_requirements` | FeatureRequirement[] | Requirements based on agent-provided features | +| `exclude_identifiers` | Identifier[] | Identifiers to always exclude | + +### Feature Requirements + +Feature requirements reference features discovered via `list_property_features`. Each agent exposes different features (consent_quality, carbon_score, coppa_certified, etc.). + +For **quantitative** features (scores, ranges): +```json +{ "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } +``` + +For **binary** features (true/false): +```json +{ "feature_id": "coppa_certified", "allowed_values": [true] } +``` + +For **categorical** features (enum values): +```json +{ "feature_id": "content_category", "allowed_values": ["news", "sports", "technology"] } +``` + +### Required Filters + +Every property list must include at least: +- One country in `countries_all` (ISO country code) +- One channel in `channels_any` (display, video, audio, etc.) + +These are required because governance agents need to know which jurisdiction and context to evaluate properties against. + +### Filter Logic + +The filter field names make the logic explicit: + +- **`countries_all`**: Property must be compliant in **ALL** listed countries. This is critical because different jurisdictions have different laws - a sugary drink ad legal in France may be illegal in the UK. +- **`channels_any`**: Property must support **ANY** of the listed channels. +- **`feature_requirements`**: Property must pass **ALL** requirements (AND). + +### Base Properties + +`base_properties` is an array of property sources to evaluate. Each entry is a **discriminated union** with `selection_type` as the discriminator: + +```json +{ + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news", "tier1"] + }, + { + "selection_type": "publisher_tags", + "publisher_domain": "mediavine.com", + "tags": ["lifestyle"] + }, + { + "selection_type": "identifiers", + "identifiers": [ + { "type": "domain", "value": "bbc.co.uk" }, + { "type": "domain", "value": "ft.com" } + ] + } + ] +} +``` + +Each entry must include `selection_type`: + +| selection_type | Required Fields | Description | +|-------------|-----------------|-------------| +| `publisher_tags` | `publisher_domain`, `tags` | All properties matching these tags within the publisher | +| `publisher_ids` | `publisher_domain`, `property_ids` | Specific property IDs within the publisher | +| `identifiers` | `identifiers` | Direct domain/app identifiers (no publisher context) | + +If `base_properties` is omitted, the agent queries its entire property database for properties matching the filters. + +See the [base-property-source schema](https://adcontextprotocol.org/schemas/v2/property/base-property-source.json) for the full specification. + +--- + +## create_property_list + +Create a new property list. + +### Request + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "UK Premium News Q1", + "description": "High-quality UK news sites for Q1 campaign", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } + ] + } + } +} +``` + +### Response + +```json +{ + "message": "Created property list 'UK Premium News Q1'.", + "context_id": "ctx-gov-list-123", + "list": { + "list_id": "pl_abc123", + "name": "UK Premium News Q1", + "description": "High-quality UK news sites for Q1 campaign", + "principal": "did:principal:brand-x", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } + ] + }, + "auth_token": "eyJhbGciOiJIUzI1NiIs...", + "created_at": "2026-01-03T16:30:00Z", + "updated_at": "2026-01-03T16:30:00Z", + "property_count": 847 + } +} +``` + +### Dynamic List (Filters Only) + +Create a list that dynamically queries the governance agent's database (no base_properties - uses agent's full coverage): + +```json +{ + "tool": "create_property_list", + "arguments": { + "name": "GDPR-Compliant DE Video", + "description": "All DE properties supporting video with strong consent", + "filters": { + "countries_all": ["DE"], + "channels_any": ["video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 90, "max_value": 100 } + ] + } + } +} +``` + +--- + +## update_property_list + +Modify an existing property list. + +### Request - Update Filters + +```json +{ + "tool": "update_property_list", + "arguments": { + "list_id": "pl_abc123", + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 80, "max_value": 100 } + ] + } + } +} +``` + +### Request - Replace Base Properties + +```json +{ + "tool": "update_property_list", + "arguments": { + "list_id": "pl_abc123", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news", "uk_tier1"] + } + ] + } +} +``` + +### Request - Add Exclusions + +```json +{ + "tool": "update_property_list", + "arguments": { + "list_id": "pl_abc123", + "filters": { + "exclude_identifiers": [ + { "type": "domain", "value": "excluded-site.com" } + ] + } + } +} +``` + +### Response + +```json +{ + "message": "Updated property list 'UK Premium News Q1'.", + "context_id": "ctx-gov-list-456", + "list": { + "list_id": "pl_abc123", + "name": "UK Premium News Q1", + "updated_at": "2026-01-03T17:00:00Z", + "property_count": 845 + } +} +``` + +--- + +## get_property_list + +Retrieve a property list with optional resolution of filters. + +### Request - Get Resolved Properties + +```json +{ + "tool": "get_property_list", + "arguments": { + "list_id": "pl_abc123", + "resolve": true, + "max_results": 100 + } +} +``` + +### Response + +The response returns a compact list of **identifiers only** (not full property objects) for efficiency. Only identifiers that pass the feature requirements are included - no scores or metadata. + +```json +{ + "message": "Retrieved property list 'UK Premium News Q1' with 847 resolved identifiers.", + "context_id": "ctx-gov-list-789", + "list_id": "pl_abc123", + "auth_token": "eyJhbGciOiJIUzI1NiIs...", + "identifiers": [ + { "type": "domain", "value": "bbc.co.uk" }, + { "type": "domain", "value": "news.sky.com" }, + { "type": "domain", "value": "ft.com" }, + { "type": "domain", "value": "theguardian.com" } + ], + "total_count": 847, + "returned_count": 100, + "pagination": { + "has_more": true, + "cursor": "eyJvZmZzZXQiOjEwMH0=" + }, + "resolved_at": "2026-01-03T17:15:00Z", + "cache_valid_until": "2026-01-04T17:15:00Z" +} +``` + +### Request - Get Metadata Only + +```json +{ + "tool": "get_property_list", + "arguments": { + "list_id": "pl_abc123", + "resolve": false + } +} +``` + +### Response (Metadata Only) + +```json +{ + "message": "Retrieved property list 'UK Premium News Q1' metadata.", + "context_id": "ctx-gov-list-790", + "list": { + "list_id": "pl_abc123", + "name": "UK Premium News Q1", + "description": "High-quality UK news sites for Q1 campaign", + "base_properties": [ + { + "selection_type": "publisher_tags", + "publisher_domain": "raptive.com", + "tags": ["premium_news"] + } + ], + "filters": { + "countries_all": ["UK"], + "channels_any": ["display", "video"], + "feature_requirements": [ + { "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } + ] + }, + "created_at": "2026-01-03T16:30:00Z", + "updated_at": "2026-01-03T17:00:00Z", + "property_count": 847 + } +} +``` + +--- + +## list_property_lists + +List all property lists accessible to the authenticated principal. + +### Request + +```json +{ + "tool": "list_property_lists", + "arguments": { + "name_contains": "UK", + "max_results": 50 + } +} +``` + +### Response + +```json +{ + "message": "Found 3 property lists matching 'UK'.", + "context_id": "ctx-gov-list-list-123", + "lists": [ + { + "list_id": "pl_abc123", + "name": "UK Premium News Q1", + "description": "High-quality UK news sites for Q1 campaign", + "created_at": "2026-01-03T16:30:00Z", + "updated_at": "2026-01-03T17:00:00Z", + "property_count": 847 + }, + { + "list_id": "pl_def456", + "name": "UK Sports Sites", + "description": "UK sports content for sponsorship", + "created_at": "2026-01-02T10:00:00Z", + "updated_at": "2026-01-02T10:00:00Z", + "property_count": 156 + } + ], + "total_count": 3, + "returned_count": 3, + "pagination": { + "has_more": false + } +} +``` + +--- + +## delete_property_list + +Delete a property list. + +### Request + +```json +{ + "tool": "delete_property_list", + "arguments": { + "list_id": "pl_abc123" + } +} +``` + +### Response + +```json +{ + "message": "Deleted property list 'UK Premium News Q1'.", + "context_id": "ctx-gov-list-del-123", + "deleted": true, + "list_id": "pl_abc123" +} +``` + +--- + +## Integration with Other Tasks + +### Using Lists in score_properties + +Reference a property list instead of passing properties inline: + +```json +{ + "tool": "score_properties", + "arguments": { + "property_list_ref": { + "agent_url": "https://governance.example.com", + "list_id": "pl_abc123" + }, + "scoring_context": { + "jurisdiction": "GDPR" + } + } +} +``` + +### Using Lists in Media Buys + +Pass property lists to media buy creation: + +```json +{ + "tool": "create_media_buy", + "arguments": { + "packages": [{ + "property_list_ref": { + "agent_url": "https://governance.example.com", + "list_id": "pl_abc123" + } + }] + } +} +``` + +--- + +## Error Codes + +| Code | Description | +|------|-------------| +| `LIST_NOT_FOUND` | Property list ID doesn't exist | +| `LIST_ACCESS_DENIED` | Principal doesn't have access to this list | +| `INVALID_FILTER` | Filter configuration is invalid | +| `LIST_NAME_EXISTS` | A list with this name already exists | + +## Caching and Refresh + +The `get_property_list` response includes caching guidance: + +| Field | Description | +|-------|-------------| +| `resolved_at` | When filters were applied and properties resolved | +| `cache_valid_until` | When consumers should re-fetch the list | + +**Typical flow for orchestrators/sellers:** + +```python +# Initial setup +response = governance_agent.get_property_list(list_id, resolve=True) +local_cache = build_property_lookup(response.properties) +cache_expiry = response.cache_valid_until + +# Periodic refresh (background job) +if now() >= cache_expiry: + response = governance_agent.get_property_list(list_id, resolve=True) + local_cache = build_property_lookup(response.properties) + cache_expiry = response.cache_valid_until + +# Bid time (no external calls) +def should_bid(property_domain): + return property_domain in local_cache +``` + +## Usage Notes + +1. **Setup time only**: Governance agents are not in the real-time bid path; resolve lists during campaign setup +2. **Local caching**: Orchestrators/sellers must cache resolved properties locally for bid-time decisions +3. **Refresh on schedule**: Re-fetch lists based on `cache_valid_until` (typically every 1-24 hours) +4. **Dynamic vs Static**: Use filters-only lists when you want the agent to maintain the property set; use base_properties when you need explicit control +5. **Pagination**: Large lists may require multiple requests with cursor-based pagination +6. **No score leakage**: Raw scores are kept internal to governance agents; responses contain pass/fail lists, not scores diff --git a/docs/media-buy/capability-discovery/authorized-properties.mdx b/docs/media-buy/capability-discovery/authorized-properties.mdx index 11d72b671c..13264accab 100644 --- a/docs/media-buy/capability-discovery/authorized-properties.mdx +++ b/docs/media-buy/capability-discovery/authorized-properties.mdx @@ -268,11 +268,11 @@ For complete technical details on implementing the `adagents.json` file format, - Validation code examples and error handling - Security considerations and best practices -See the **[adagents.json Tech Spec](/docs/media-buy/capability-discovery/adagents)** for complete implementation guidance. +See the **[adagents.json Tech Spec](/docs/governance/property/adagents)** for complete implementation guidance. ## Related Documentation - **[`list_authorized_properties`](/docs/media-buy/task-reference/list_authorized_properties)** - API reference for property discovery - **[Product Discovery](/docs/media-buy/product-discovery/)** - How authorization integrates with product discovery - **[Properties Schema](https://adcontextprotocol.org/schemas/v2/core/property.json)** - Technical property data model -- **[adagents.json Tech Spec](/docs/media-buy/capability-discovery/adagents)** - Complete `adagents.json` implementation guide \ No newline at end of file +- **[adagents.json Tech Spec](/docs/governance/property/adagents)** - Complete `adagents.json` implementation guide \ No newline at end of file diff --git a/docs/media-buy/capability-discovery/index.mdx b/docs/media-buy/capability-discovery/index.mdx index 885a0e16c9..76091f5049 100644 --- a/docs/media-buy/capability-discovery/index.mdx +++ b/docs/media-buy/capability-discovery/index.mdx @@ -30,7 +30,7 @@ Learn how AdCP prevents unauthorized resale and ensures sales agents are legitim - Property tags and large-scale authorization management -**Cross-Protocol Foundation**: The `adagents.json` authorization system applies across ALL AdCP protocols (Media Buy, Signals, and future Curation). See the [adagents.json specification](/docs/media-buy/capability-discovery/adagents) for complete implementation details. +**Cross-Protocol Foundation**: The `adagents.json` authorization system applies across ALL AdCP protocols (Media Buy, Signals, and future Curation). See the [adagents.json specification](/docs/governance/property/adagents) for complete implementation details. @@ -80,4 +80,4 @@ Together, these capabilities provide the foundation for safe, efficient, and eff - **[Creative Protocol](/docs/creative/)** - Creative agents and manifests - **[Creative Channel Guides](/docs/creative/channels/video)** - Format examples and patterns - **[Creative Manifests](/docs/creative/creative-manifests)** - Understanding creative specifications -- **[adagents.json Specification](/docs/media-buy/capability-discovery/adagents)** - Publisher authorization system (applies across all AdCP protocols) \ No newline at end of file +- **[adagents.json Specification](/docs/governance/property/adagents)** - Publisher authorization system (applies across all AdCP protocols) \ No newline at end of file diff --git a/docs/media-buy/task-reference/list_authorized_properties.mdx b/docs/media-buy/task-reference/list_authorized_properties.mdx index 5e7cb8ea9d..55bd3fcf87 100644 --- a/docs/media-buy/task-reference/list_authorized_properties.mdx +++ b/docs/media-buy/task-reference/list_authorized_properties.mdx @@ -498,6 +498,6 @@ After discovering authorized properties: ## Learn More -- [adagents.json Specification](/docs/media-buy/capability-discovery/adagents) - Publisher authorization file format +- [adagents.json Specification](/docs/governance/property/adagents) - Publisher authorization file format - [Property Schema](https://adcontextprotocol.org/schemas/v2/core/property.json) - Property definition structure - [Authorization Guide](/docs/reference/authentication) - How authorization works in AdCP diff --git a/docs/reference/implementor-faq.mdx b/docs/reference/implementor-faq.mdx index d0c10b7015..0c76f825ce 100644 --- a/docs/reference/implementor-faq.mdx +++ b/docs/reference/implementor-faq.mdx @@ -289,7 +289,7 @@ assert(response.message.includes("policy")); 3. Verify the sales agent URL appears in `authorized_agents` 4. Reject products from unauthorized agents -See [Authorization Validation](/docs/media-buy/capability-discovery/adagents#buyer-agent-validation) for complete requirements. +See [Authorization Validation](/docs/governance/property/adagents#buyer-agent-validation) for complete requirements. ## Terminology diff --git a/docs/reference/roadmap.mdx b/docs/reference/roadmap.mdx index 35b1ec2d61..137ddeb29e 100644 --- a/docs/reference/roadmap.mdx +++ b/docs/reference/roadmap.mdx @@ -59,9 +59,9 @@ These features are in active development but may not make the 3.0 release: The following features are in the working group planning stages: -### Governance Protocol +### Governance Protocol [3.0] -A framework for multi-party approval workflows and delegated authority management. See the [Governance Protocol Working Document](https://docs.google.com/document/d/1osokTr5Xk2PyLBUHbx2jpPad0TLrQIDh2I6ZWzpHMmY/edit?tab=t.0) for details. +Property governance (property lists, filters, adagents.json authorization), brand standards (brand manifests, creative guidelines), and compliance controls. See the [Governance Protocol](/docs/governance/index) for current specification. ### Private Marketplace (PMP) Support - Deal ID integration diff --git a/static/schemas/source/core/identifier.json b/static/schemas/source/core/identifier.json new file mode 100644 index 0000000000..f7e68aabe2 --- /dev/null +++ b/static/schemas/source/core/identifier.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/identifier.json", + "title": "Identifier", + "description": "A property identifier with type and value. Used to identify properties across platforms (domains, app store IDs, etc.).", + "type": "object", + "properties": { + "type": { + "$ref": "/schemas/enums/identifier-types.json", + "description": "Type of identifier" + }, + "value": { + "type": "string", + "description": "The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" + } + }, + "required": ["type", "value"], + "additionalProperties": false +} diff --git a/static/schemas/source/core/property-list-ref.json b/static/schemas/source/core/property-list-ref.json new file mode 100644 index 0000000000..06e5ced9ba --- /dev/null +++ b/static/schemas/source/core/property-list-ref.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/property-list-ref.json", + "title": "Property List Reference", + "description": "Reference to an externally managed property list. Enables passing large property sets (50,000+) without embedding them in requests. The receiving agent fetches and caches the list independently.", + "type": "object", + "properties": { + "agent_url": { + "type": "string", + "format": "uri", + "description": "URL of the agent managing the property list" + }, + "list_id": { + "type": "string", + "description": "Identifier for the property list within the agent", + "minLength": 1 + }, + "auth_token": { + "type": "string", + "description": "JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access." + } + }, + "required": ["agent_url", "list_id"], + "additionalProperties": false +} diff --git a/static/schemas/source/enums/adcp-domain.json b/static/schemas/source/enums/adcp-domain.json index 44f2828c6e..ee392aeff0 100644 --- a/static/schemas/source/enums/adcp-domain.json +++ b/static/schemas/source/enums/adcp-domain.json @@ -4,5 +4,11 @@ "title": "AdCP Domain", "description": "AdCP protocol domains for task categorization", "type": "string", - "enum": ["media-buy", "signals"] + "enum": ["media-buy", "signals", "governance", "creative"], + "enumDescriptions": { + "media-buy": "Campaign creation, package management, and delivery optimization", + "signals": "Audience signal discovery and activation", + "governance": "Property governance (identity, authorization, data, selection), brand standards, and compliance", + "creative": "Creative asset management, format discovery, and rendering" + } } diff --git a/static/schemas/source/enums/task-type.json b/static/schemas/source/enums/task-type.json index 063291bb42..7af80e9b9d 100644 --- a/static/schemas/source/enums/task-type.json +++ b/static/schemas/source/enums/task-type.json @@ -9,18 +9,30 @@ "update_media_buy", "sync_creatives", "activate_signal", - "get_signals" + "get_signals", + "list_property_features", + "create_property_list", + "update_property_list", + "get_property_list", + "list_property_lists", + "delete_property_list" ], "enumDescriptions": { "create_media_buy": "Media-buy domain: Create a new advertising campaign with one or more packages", "update_media_buy": "Media-buy domain: Update campaign settings, package configuration, or delivery parameters", "sync_creatives": "Media-buy domain: Sync creative assets to publisher's library with upsert semantics", "activate_signal": "Signals domain: Activate an audience signal on a specific platform or account", - "get_signals": "Signals domain: Discover available audience signals based on natural language description" + "get_signals": "Signals domain: Discover available audience signals based on natural language description", + "list_property_features": "Property domain: Discover what features a governance agent can evaluate", + "create_property_list": "Property domain: Create a new property list with filters and brand manifest", + "update_property_list": "Property domain: Update an existing property list", + "get_property_list": "Property domain: Retrieve a property list with resolved properties", + "list_property_lists": "Property domain: List all accessible property lists", + "delete_property_list": "Property domain: Delete a property list" }, "notes": [ "Task types map to specific AdCP task operations", - "Each task type belongs to either the 'media-buy' or 'signals' domain", + "Each task type belongs to the 'media-buy', 'signals', 'property', or 'creative' domain", "This enum is used in task management APIs (tasks/list, tasks/get) and webhook payloads", "New task types require a minor version bump per semantic versioning" ] diff --git a/static/schemas/source/index.json b/static/schemas/source/index.json index 2056c90cee..06182baa82 100644 --- a/static/schemas/source/index.json +++ b/static/schemas/source/index.json @@ -168,6 +168,14 @@ "property-tag": { "$ref": "/schemas/core/property-tag.json", "description": "Tag for categorizing publisher properties - lowercase alphanumeric with underscores only" + }, + "property-list-ref": { + "$ref": "/schemas/core/property-list-ref.json", + "description": "Reference to an externally managed property list for passing large property sets" + }, + "identifier": { + "$ref": "/schemas/core/identifier.json", + "description": "A property identifier with type and value" } } }, @@ -220,7 +228,7 @@ }, "task-type": { "$ref": "/schemas/enums/task-type.json", - "description": "Valid AdCP task types across all domains (create_media_buy, update_media_buy, sync_creatives, activate_signal, get_signals)" + "description": "Valid AdCP task types across all domains (create_media_buy, update_media_buy, sync_creatives, activate_signal, get_signals, list_property_features, create_property_list, get_property_list, update_property_list, delete_property_list)" }, "asset-content-type": { "$ref": "/schemas/enums/asset-content-type.json", @@ -280,7 +288,7 @@ }, "adcp-domain": { "$ref": "/schemas/enums/adcp-domain.json", - "description": "AdCP protocol domains (media-buy, signals)" + "description": "AdCP protocol domains (media-buy, signals, governance)" }, "http-method": { "$ref": "/schemas/enums/http-method.json", @@ -562,6 +570,105 @@ } } }, + "governance": { + "description": "Governance protocol for property governance, brand standards, and compliance", + "supporting-schemas": { + "property-feature-definition": { + "$ref": "/schemas/property/property-feature-definition.json", + "description": "Definition of a feature that a governance agent can evaluate" + }, + "property-feature": { + "$ref": "/schemas/property/property-feature.json", + "description": "A discrete feature assessment for a property" + }, + "feature-requirement": { + "$ref": "/schemas/property/feature-requirement.json", + "description": "A feature-based requirement for property filtering" + }, + "property-error": { + "$ref": "/schemas/property/property-error.json", + "description": "Error information for a property that could not be evaluated" + }, + "property-list": { + "$ref": "/schemas/property/property-list.json", + "description": "A managed property list with optional filters for dynamic evaluation" + }, + "property-list-filters": { + "$ref": "/schemas/property/property-list-filters.json", + "description": "Filters that dynamically modify a property list when resolved" + }, + "property-list-changed-webhook": { + "$ref": "/schemas/property/property-list-changed-webhook.json", + "description": "Webhook payload when a property list changes" + }, + "base-property-source": { + "$ref": "/schemas/property/base-property-source.json", + "description": "A source of properties for a property list - supports publisher+tags, publisher+property_ids, or direct identifiers" + } + }, + "tasks": { + "list-property-features": { + "request": { + "$ref": "/schemas/property/list-property-features-request.json", + "description": "Request parameters for discovering governance agent capabilities" + }, + "response": { + "$ref": "/schemas/property/list-property-features-response.json", + "description": "Response payload for list_property_features task" + } + }, + "create-property-list": { + "request": { + "$ref": "/schemas/property/create-property-list-request.json", + "description": "Request parameters for creating a new property list" + }, + "response": { + "$ref": "/schemas/property/create-property-list-response.json", + "description": "Response payload for create_property_list task" + } + }, + "update-property-list": { + "request": { + "$ref": "/schemas/property/update-property-list-request.json", + "description": "Request parameters for updating an existing property list" + }, + "response": { + "$ref": "/schemas/property/update-property-list-response.json", + "description": "Response payload for update_property_list task" + } + }, + "get-property-list": { + "request": { + "$ref": "/schemas/property/get-property-list-request.json", + "description": "Request parameters for retrieving a property list with resolved properties" + }, + "response": { + "$ref": "/schemas/property/get-property-list-response.json", + "description": "Response payload for get_property_list task" + } + }, + "list-property-lists": { + "request": { + "$ref": "/schemas/property/list-property-lists-request.json", + "description": "Request parameters for listing property lists" + }, + "response": { + "$ref": "/schemas/property/list-property-lists-response.json", + "description": "Response payload for list_property_lists task" + } + }, + "delete-property-list": { + "request": { + "$ref": "/schemas/property/delete-property-list-request.json", + "description": "Request parameters for deleting a property list" + }, + "response": { + "$ref": "/schemas/property/delete-property-list-response.json", + "description": "Response payload for delete_property_list task" + } + } + } + }, "adagents": { "description": "Authorized sales agents file format specification", "$ref": "/schemas/adagents.json", diff --git a/static/schemas/source/property/base-property-source.json b/static/schemas/source/property/base-property-source.json new file mode 100644 index 0000000000..d23b0c2bc3 --- /dev/null +++ b/static/schemas/source/property/base-property-source.json @@ -0,0 +1,87 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/base-property-source.json", + "title": "Base Property Source", + "description": "A source of properties for a property list. Supports three selection patterns: publisher with tags, publisher with property IDs, or direct identifiers.", + "discriminator": { + "propertyName": "selection_type" + }, + "oneOf": [ + { + "type": "object", + "title": "Publisher Tags Source", + "description": "Select properties from a publisher by tag membership", + "properties": { + "selection_type": { + "type": "string", + "const": "publisher_tags", + "description": "Discriminator indicating selection by property tags within a publisher" + }, + "publisher_domain": { + "type": "string", + "description": "Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$" + }, + "tags": { + "type": "array", + "description": "Property tags from the publisher's adagents.json. Selects all properties with these tags.", + "items": { + "$ref": "/schemas/core/property-tag.json" + }, + "minItems": 1 + } + }, + "required": ["selection_type", "publisher_domain", "tags"], + "additionalProperties": false + }, + { + "type": "object", + "title": "Publisher Property IDs Source", + "description": "Select specific properties from a publisher by ID", + "properties": { + "selection_type": { + "type": "string", + "const": "publisher_ids", + "description": "Discriminator indicating selection by specific property IDs within a publisher" + }, + "publisher_domain": { + "type": "string", + "description": "Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$" + }, + "property_ids": { + "type": "array", + "description": "Specific property IDs from the publisher's adagents.json", + "items": { + "$ref": "/schemas/core/property-id.json" + }, + "minItems": 1 + } + }, + "required": ["selection_type", "publisher_domain", "property_ids"], + "additionalProperties": false + }, + { + "type": "object", + "title": "Direct Identifiers Source", + "description": "Select properties by direct identifiers (domains, app IDs, etc.) without publisher context", + "properties": { + "selection_type": { + "type": "string", + "const": "identifiers", + "description": "Discriminator indicating selection by direct identifiers" + }, + "identifiers": { + "type": "array", + "description": "Direct property identifiers (domains, app IDs, etc.)", + "items": { + "$ref": "/schemas/core/identifier.json" + }, + "minItems": 1 + } + }, + "required": ["selection_type", "identifiers"], + "additionalProperties": false + } + ] +} diff --git a/static/schemas/source/property/create-property-list-request.json b/static/schemas/source/property/create-property-list-request.json new file mode 100644 index 0000000000..b1c3185f11 --- /dev/null +++ b/static/schemas/source/property/create-property-list-request.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/create-property-list-request.json", + "title": "Create Property List Request", + "description": "Request parameters for creating a new property list", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Human-readable name for the list" + }, + "description": { + "type": "string", + "description": "Description of the list's purpose" + }, + "base_properties": { + "type": "array", + "description": "Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database.", + "items": { + "$ref": "/schemas/property/base-property-source.json" + } + }, + "filters": { + "$ref": "/schemas/property/property-list-filters.json", + "description": "Dynamic filters to apply when resolving the list" + }, + "brand_manifest": { + "$ref": "/schemas/core/brand-manifest.json", + "description": "Brand identity and requirements. When provided, the agent automatically applies appropriate rules based on brand characteristics (industry, target_audience, etc.)." + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["name"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/create-property-list-response.json b/static/schemas/source/property/create-property-list-response.json new file mode 100644 index 0000000000..00ced0d510 --- /dev/null +++ b/static/schemas/source/property/create-property-list-response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/create-property-list-response.json", + "title": "Create Property List Response", + "description": "Response payload for create_property_list task", + "type": "object", + "properties": { + "list": { + "$ref": "/schemas/property/property-list.json", + "description": "The created property list" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/delete-property-list-request.json b/static/schemas/source/property/delete-property-list-request.json new file mode 100644 index 0000000000..0e5217d260 --- /dev/null +++ b/static/schemas/source/property/delete-property-list-request.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/delete-property-list-request.json", + "title": "Delete Property List Request", + "description": "Request parameters for deleting a property list", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list to delete" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list_id"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/delete-property-list-response.json b/static/schemas/source/property/delete-property-list-response.json new file mode 100644 index 0000000000..655bbed524 --- /dev/null +++ b/static/schemas/source/property/delete-property-list-response.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/delete-property-list-response.json", + "title": "Delete Property List Response", + "description": "Response payload for delete_property_list task", + "type": "object", + "properties": { + "deleted": { + "type": "boolean", + "description": "Whether the list was successfully deleted" + }, + "list_id": { + "type": "string", + "description": "ID of the deleted list" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["deleted", "list_id"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/feature-requirement.json b/static/schemas/source/property/feature-requirement.json new file mode 100644 index 0000000000..3eae30c929 --- /dev/null +++ b/static/schemas/source/property/feature-requirement.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/feature-requirement.json", + "title": "Feature Requirement", + "description": "A feature-based requirement for property filtering. Use min_value/max_value for quantitative features, allowed_values for binary/categorical features.", + "type": "object", + "properties": { + "feature_id": { + "type": "string", + "description": "Feature to evaluate (discovered via list_property_features)" + }, + "min_value": { + "type": "number", + "description": "Minimum numeric value required (for quantitative features)" + }, + "max_value": { + "type": "number", + "description": "Maximum numeric value allowed (for quantitative features)" + }, + "allowed_values": { + "type": "array", + "description": "Values that pass the requirement (for binary/categorical features)", + "items": {} + } + }, + "required": ["feature_id"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/get-property-list-request.json b/static/schemas/source/property/get-property-list-request.json new file mode 100644 index 0000000000..1fd852a761 --- /dev/null +++ b/static/schemas/source/property/get-property-list-request.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/get-property-list-request.json", + "title": "Get Property List Request", + "description": "Request parameters for retrieving a property list with resolved identifiers", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list to retrieve" + }, + "resolve": { + "type": "boolean", + "description": "Whether to apply filters and return resolved identifiers (default: true)", + "default": true + }, + "max_results": { + "type": "integer", + "description": "Maximum identifiers to return (for large lists)", + "minimum": 1 + }, + "cursor": { + "type": "string", + "description": "Pagination cursor for large result sets" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list_id"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/get-property-list-response.json b/static/schemas/source/property/get-property-list-response.json new file mode 100644 index 0000000000..ea35b8c930 --- /dev/null +++ b/static/schemas/source/property/get-property-list-response.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/get-property-list-response.json", + "title": "Get Property List Response", + "description": "Response payload for get_property_list task. Returns identifiers only (not full property objects or scores). Consumers should cache the resolved identifiers and refresh based on cache_valid_until.", + "type": "object", + "properties": { + "list": { + "$ref": "/schemas/property/property-list.json", + "description": "The property list metadata (always returned)" + }, + "auth_token": { + "type": "string", + "description": "Token that can be shared with sellers to authorize fetching this list" + }, + "identifiers": { + "type": "array", + "description": "Resolved identifiers that passed filters (if resolve=true). Cache these locally for real-time use.", + "items": { + "$ref": "/schemas/core/identifier.json" + } + }, + "total_count": { + "type": "integer", + "description": "Total number of identifiers in resolved list" + }, + "returned_count": { + "type": "integer", + "description": "Number of identifiers returned in this response" + }, + "pagination": { + "type": "object", + "description": "Pagination information", + "properties": { + "has_more": { + "type": "boolean", + "description": "Whether more results are available" + }, + "cursor": { + "type": "string", + "description": "Cursor for next page" + } + }, + "additionalProperties": false + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "description": "When the list was resolved" + }, + "cache_valid_until": { + "type": "string", + "format": "date-time", + "description": "Cache expiration timestamp. Re-fetch the list after this time to get updated identifiers." + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/list-property-features-request.json b/static/schemas/source/property/list-property-features-request.json new file mode 100644 index 0000000000..3c16d096c1 --- /dev/null +++ b/static/schemas/source/property/list-property-features-request.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/list-property-features-request.json", + "title": "List Property Features Request", + "description": "Request payload for list_property_features task. Discovers what features a governance agent can evaluate.", + "type": "object", + "properties": { + "property_types": { + "type": "array", + "description": "Filter to features available for these property types", + "items": { + "type": "string" + } + }, + "countries": { + "type": "array", + "description": "Filter to features available in these countries", + "items": { + "type": "string" + } + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "additionalProperties": false +} diff --git a/static/schemas/source/property/list-property-features-response.json b/static/schemas/source/property/list-property-features-response.json new file mode 100644 index 0000000000..efbf849fd4 --- /dev/null +++ b/static/schemas/source/property/list-property-features-response.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/list-property-features-response.json", + "title": "List Property Features Response", + "description": "Response payload for list_property_features task. Returns the features this governance agent can evaluate.", + "type": "object", + "properties": { + "features": { + "type": "array", + "description": "Features this agent can evaluate", + "items": { + "$ref": "/schemas/property/property-feature-definition.json" + } + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["features"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/list-property-lists-request.json b/static/schemas/source/property/list-property-lists-request.json new file mode 100644 index 0000000000..034c38e475 --- /dev/null +++ b/static/schemas/source/property/list-property-lists-request.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/list-property-lists-request.json", + "title": "List Property Lists Request", + "description": "Request parameters for listing property lists", + "type": "object", + "properties": { + "principal": { + "type": "string", + "description": "Filter to lists owned by this principal" + }, + "name_contains": { + "type": "string", + "description": "Filter to lists whose name contains this string" + }, + "max_results": { + "type": "integer", + "description": "Maximum lists to return", + "minimum": 1, + "default": 100 + }, + "cursor": { + "type": "string", + "description": "Pagination cursor" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "additionalProperties": false +} diff --git a/static/schemas/source/property/list-property-lists-response.json b/static/schemas/source/property/list-property-lists-response.json new file mode 100644 index 0000000000..f80b67ed17 --- /dev/null +++ b/static/schemas/source/property/list-property-lists-response.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/list-property-lists-response.json", + "title": "List Property Lists Response", + "description": "Response payload for list_property_lists task", + "type": "object", + "properties": { + "lists": { + "type": "array", + "description": "Array of property lists (metadata only, not resolved properties)", + "items": { + "$ref": "/schemas/property/property-list.json" + } + }, + "total_count": { + "type": "integer", + "description": "Total number of lists matching criteria" + }, + "returned_count": { + "type": "integer", + "description": "Number of lists returned in this response" + }, + "pagination": { + "type": "object", + "description": "Pagination information", + "properties": { + "has_more": { + "type": "boolean", + "description": "Whether more results are available" + }, + "cursor": { + "type": "string", + "description": "Cursor for next page" + } + }, + "additionalProperties": false + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["lists"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-error.json b/static/schemas/source/property/property-error.json new file mode 100644 index 0000000000..7b46b096ef --- /dev/null +++ b/static/schemas/source/property/property-error.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-error.json", + "title": "Property Error", + "description": "Error information for a property that could not be evaluated", + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Error code", + "enum": [ + "PROPERTY_NOT_FOUND", + "PROPERTY_NOT_MONITORED", + "LIST_NOT_FOUND", + "LIST_ACCESS_DENIED", + "METHODOLOGY_NOT_SUPPORTED", + "JURISDICTION_NOT_SUPPORTED" + ] + }, + "property": { + "$ref": "/schemas/core/property.json", + "description": "The property that caused the error" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": ["code", "message"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-feature-definition.json b/static/schemas/source/property/property-feature-definition.json new file mode 100644 index 0000000000..91ca511231 --- /dev/null +++ b/static/schemas/source/property/property-feature-definition.json @@ -0,0 +1,75 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-feature-definition.json", + "title": "Property Feature Definition", + "description": "Defines a feature that a governance agent can evaluate for properties. Used in list_property_features to advertise agent capabilities.", + "type": "object", + "properties": { + "feature_id": { + "type": "string", + "description": "Unique identifier for this feature (e.g., 'consent_quality', 'carbon_score', 'coppa_certified')" + }, + "name": { + "type": "string", + "description": "Human-readable name for the feature" + }, + "description": { + "type": "string", + "description": "Description of what this feature measures or represents" + }, + "type": { + "type": "string", + "enum": ["binary", "quantitative", "categorical"], + "description": "The type of values this feature produces: binary (true/false), quantitative (numeric range), categorical (enumerated values)" + }, + "range": { + "type": "object", + "description": "For quantitative features, the valid range of values", + "properties": { + "min": { + "type": "number", + "description": "Minimum value" + }, + "max": { + "type": "number", + "description": "Maximum value" + } + }, + "required": ["min", "max"], + "additionalProperties": false + }, + "allowed_values": { + "type": "array", + "description": "For categorical features, the set of valid values", + "items": { + "type": "string" + } + }, + "coverage": { + "type": "object", + "description": "What this feature covers (empty arrays = all)", + "properties": { + "property_types": { + "type": "array", + "description": "Property types this feature applies to", + "items": { + "type": "string" + } + }, + "countries": { + "type": "array", + "description": "Countries where this feature is available", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["feature_id", "name", "type"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-feature.json b/static/schemas/source/property/property-feature.json new file mode 100644 index 0000000000..6c9939b595 --- /dev/null +++ b/static/schemas/source/property/property-feature.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-feature.json", + "title": "Property Feature", + "description": "A discrete feature assessment for a property (e.g., from app store privacy labels)", + "type": "object", + "properties": { + "feature_id": { + "type": "string", + "description": "Identifier for the feature being assessed" + }, + "value": { + "type": "string", + "description": "The feature value" + }, + "source": { + "type": "string", + "description": "Source of the feature data (e.g., app_store_privacy_label, tcf_string)" + } + }, + "required": ["feature_id", "value"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-list-changed-webhook.json b/static/schemas/source/property/property-list-changed-webhook.json new file mode 100644 index 0000000000..482c325bcc --- /dev/null +++ b/static/schemas/source/property/property-list-changed-webhook.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-list-changed-webhook.json", + "title": "Property List Changed Webhook", + "description": "Webhook notification sent when a property list's resolved properties change. Contains a summary only - recipients must call get_property_list to retrieve the updated properties. This keeps payloads small and avoids redundant data transfer.", + "type": "object", + "properties": { + "event": { + "type": "string", + "const": "property_list_changed", + "description": "The event type" + }, + "list_id": { + "type": "string", + "description": "ID of the property list that changed" + }, + "list_name": { + "type": "string", + "description": "Name of the property list" + }, + "change_summary": { + "type": "object", + "description": "Summary of changes to the resolved list", + "properties": { + "properties_added": { + "type": "integer", + "description": "Number of properties added since last resolution" + }, + "properties_removed": { + "type": "integer", + "description": "Number of properties removed since last resolution" + }, + "total_properties": { + "type": "integer", + "description": "Total properties in the resolved list" + } + }, + "additionalProperties": false + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "description": "When the list was re-resolved" + }, + "cache_valid_until": { + "type": "string", + "format": "date-time", + "description": "When the consumer should refresh from the governance agent" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["event", "list_id", "resolved_at"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-list-filters.json b/static/schemas/source/property/property-list-filters.json new file mode 100644 index 0000000000..d9e6ed5bc2 --- /dev/null +++ b/static/schemas/source/property/property-list-filters.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-list-filters.json", + "title": "Property List Filters", + "description": "Filters that dynamically modify a property list when resolved", + "type": "object", + "properties": { + "countries_all": { + "type": "array", + "description": "Property must be compliant in ALL listed countries (ISO codes). Required.", + "items": { + "type": "string", + "pattern": "^[A-Z]{2}$" + } + }, + "channels_any": { + "type": "array", + "description": "Property must support ANY of the listed channels. Required.", + "items": { + "$ref": "/schemas/enums/channels.json" + } + }, + "property_types": { + "type": "array", + "description": "Filter to these property types", + "items": { + "$ref": "/schemas/enums/property-type.json" + } + }, + "feature_requirements": { + "type": "array", + "description": "Feature-based requirements. Property must pass ALL requirements (AND logic).", + "items": { + "$ref": "/schemas/property/feature-requirement.json" + } + }, + "exclude_identifiers": { + "type": "array", + "description": "Identifiers to always exclude from results", + "items": { + "$ref": "/schemas/core/identifier.json" + } + } + }, + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-list.json b/static/schemas/source/property/property-list.json new file mode 100644 index 0000000000..53a89a5cf3 --- /dev/null +++ b/static/schemas/source/property/property-list.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-list.json", + "title": "Property List", + "description": "A managed property list with optional filters for dynamic evaluation. Lists are resolved at setup time and cached by orchestrators/sellers for real-time use.", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "Unique identifier for this property list" + }, + "name": { + "type": "string", + "description": "Human-readable name for the list" + }, + "description": { + "type": "string", + "description": "Description of the list's purpose" + }, + "principal": { + "type": "string", + "description": "Principal identity that owns this list" + }, + "base_properties": { + "type": "array", + "description": "Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database.", + "items": { + "$ref": "/schemas/property/base-property-source.json" + } + }, + "filters": { + "$ref": "/schemas/property/property-list-filters.json", + "description": "Dynamic filters applied when resolving the list" + }, + "brand_manifest": { + "$ref": "/schemas/core/brand-manifest.json", + "description": "Brand identity used to automatically apply appropriate rules" + }, + "webhook_url": { + "type": "string", + "format": "uri", + "description": "URL to receive notifications when the resolved list changes" + }, + "cache_duration_hours": { + "type": "integer", + "description": "Recommended cache duration for resolved list. Consumers should re-fetch after this period.", + "minimum": 1, + "default": 24 + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the list was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the list was last modified" + }, + "property_count": { + "type": "integer", + "description": "Number of properties in the resolved list (at time of last resolution)" + } + }, + "required": ["list_id", "name"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/update-property-list-request.json b/static/schemas/source/property/update-property-list-request.json new file mode 100644 index 0000000000..d83cdb49b0 --- /dev/null +++ b/static/schemas/source/property/update-property-list-request.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/update-property-list-request.json", + "title": "Update Property List Request", + "description": "Request parameters for updating an existing property list", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list to update" + }, + "name": { + "type": "string", + "description": "New name for the list" + }, + "description": { + "type": "string", + "description": "New description" + }, + "base_properties": { + "type": "array", + "description": "Complete replacement for the base properties list (not a patch). Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers).", + "items": { + "$ref": "/schemas/property/base-property-source.json" + } + }, + "filters": { + "$ref": "/schemas/property/property-list-filters.json", + "description": "Complete replacement for the filters (not a patch)" + }, + "brand_manifest": { + "$ref": "/schemas/core/brand-manifest.json", + "description": "Update brand identity and requirements" + }, + "webhook_url": { + "type": "string", + "format": "uri", + "description": "Update the webhook URL for list change notifications (set to empty string to remove)" + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list_id"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/update-property-list-response.json b/static/schemas/source/property/update-property-list-response.json new file mode 100644 index 0000000000..8258df259c --- /dev/null +++ b/static/schemas/source/property/update-property-list-response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/update-property-list-response.json", + "title": "Update Property List Response", + "description": "Response payload for update_property_list task", + "type": "object", + "properties": { + "list": { + "$ref": "/schemas/property/property-list.json", + "description": "The updated property list" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list"], + "additionalProperties": false +} From 06c3b638c822fe3862a829a20ec176f4fc52095a Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 12:26:07 -0500 Subject: [PATCH 02/19] Add changeset for Property Governance Protocol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .changeset/property-governance-protocol.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/property-governance-protocol.md diff --git a/.changeset/property-governance-protocol.md b/.changeset/property-governance-protocol.md new file mode 100644 index 0000000000..eab844f59f --- /dev/null +++ b/.changeset/property-governance-protocol.md @@ -0,0 +1,13 @@ +--- +"adcontextprotocol": minor +--- + +Add Property Governance Protocol for AdCP 3.0 + +Introduces stateful property list management for governance agents: + +- Property list CRUD operations with filters and brand manifests +- Feature discovery via `list_property_features` +- Discriminated unions for base property sources (`selection_type` discriminator) +- Identifiers-only responses (scores stay internal to governance agents) +- Webhook notifications for list changes From 5bc405a87ae1d3cb834d8a7f658495845a8e9e1b Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 12:37:26 -0500 Subject: [PATCH 03/19] Use empty changeset for draft 3.0 proposal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No version bump needed for draft/proposal specs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .changeset/property-governance-protocol.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.changeset/property-governance-protocol.md b/.changeset/property-governance-protocol.md index eab844f59f..d3f767619c 100644 --- a/.changeset/property-governance-protocol.md +++ b/.changeset/property-governance-protocol.md @@ -1,13 +1,6 @@ --- -"adcontextprotocol": minor --- -Add Property Governance Protocol for AdCP 3.0 +[DRAFT] Property Governance Protocol for AdCP 3.0 -Introduces stateful property list management for governance agents: - -- Property list CRUD operations with filters and brand manifests -- Feature discovery via `list_property_features` -- Discriminated unions for base property sources (`selection_type` discriminator) -- Identifiers-only responses (scores stay internal to governance agents) -- Webhook notifications for list changes +This is a proposal/draft specification that does not yet affect the released protocol version. No version bump required. From cdccb3dca5f9ea1d04582c90d0641442698323d4 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 13:45:59 -0500 Subject: [PATCH 04/19] Address review feedback on Property Governance Protocol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Schema fixes: - Add methodology_url (required) and methodology_version to property-feature-definition - Add signature field (required) to webhook schema for security - Make countries_all and channels_any required in property-list-filters - Move auth_token from get_property_list to create_property_list response Documentation fixes: - Add terminology note: Orchestrator = Buyer Agent - Update examples to show auth_token in create response only - Add note that auth_token is only returned at creation time 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/property/specification.mdx | 4 ++-- docs/governance/property/tasks/index.mdx | 12 +++++++----- docs/governance/property/tasks/property_lists.mdx | 9 ++++++--- .../property/create-property-list-response.json | 6 +++++- .../source/property/get-property-list-response.json | 4 ---- .../source/property/property-feature-definition.json | 11 ++++++++++- .../property/property-list-changed-webhook.json | 6 +++++- .../source/property/property-list-filters.json | 1 + 8 files changed, 36 insertions(+), 17 deletions(-) diff --git a/docs/governance/property/specification.mdx b/docs/governance/property/specification.mdx index a000a3a221..5c636be3e6 100644 --- a/docs/governance/property/specification.mdx +++ b/docs/governance/property/specification.mdx @@ -45,8 +45,8 @@ All evaluation (scoring, filtering, discovery) happens implicitly when property Every governance request involves two key roles: -#### Orchestrator -The platform or system making the API request to the governance agent: +#### Orchestrator (Buyer Agent) +The platform or system making the API request to the governance agent. In AdCP documentation, this role is often called a "buyer agent" when operating in the media buying context. - **Examples**: DSP, trading desk platform, campaign management tool - **Responsibilities**: Makes API calls, handles authentication, manages the technical interaction - **Account**: Has technical credentials and API access to the governance agent diff --git a/docs/governance/property/tasks/index.mdx b/docs/governance/property/tasks/index.mdx index e7cdac9501..c6f8f65736 100644 --- a/docs/governance/property/tasks/index.mdx +++ b/docs/governance/property/tasks/index.mdx @@ -88,7 +88,7 @@ Filters have two built-in fields (`countries_all`, `channels_any`) plus `feature ### Getting Resolved Properties -Use `get_property_list` to retrieve the list. You'll receive an `auth_token` that can be shared with sellers: +Use `get_property_list` to retrieve the list with resolved identifiers: ```json { @@ -100,18 +100,20 @@ Use `get_property_list` to retrieve the list. You'll receive an `auth_token` tha } ``` -Response includes an auth token for sharing. Note that **raw scores are not returned** - only properties that pass the filter thresholds are included: +Response includes resolved identifiers. Note that **raw scores are not returned** - only identifiers that pass the filter thresholds are included: ```json { "list_id": "pl_abc123", - "auth_token": "eyJhbGciOiJIUzI1NiIs...", - "properties": [...], + "identifiers": [ + { "type": "domain", "value": "bbc.co.uk" }, + { "type": "domain", "value": "news.sky.com" } + ], "cache_valid_until": "2026-01-04T17:15:00Z" } ``` -Pass the `auth_token` to sellers so they can fetch and cache the resolved list. +The `auth_token` for sharing with sellers is returned at creation time (from `create_property_list`). Store it securely - it's only returned once. ### Multi-Agent Integration diff --git a/docs/governance/property/tasks/property_lists.mdx b/docs/governance/property/tasks/property_lists.mdx index e2f3afd191..c6c19976fb 100644 --- a/docs/governance/property/tasks/property_lists.mdx +++ b/docs/governance/property/tasks/property_lists.mdx @@ -306,11 +306,11 @@ Create a new property list. { "feature_id": "consent_quality", "min_value": 85, "max_value": 100 } ] }, - "auth_token": "eyJhbGciOiJIUzI1NiIs...", "created_at": "2026-01-03T16:30:00Z", "updated_at": "2026-01-03T16:30:00Z", "property_count": 847 - } + }, + "auth_token": "eyJhbGciOiJIUzI1NiIs..." } ``` @@ -436,7 +436,6 @@ The response returns a compact list of **identifiers only** (not full property o "message": "Retrieved property list 'UK Premium News Q1' with 847 resolved identifiers.", "context_id": "ctx-gov-list-789", "list_id": "pl_abc123", - "auth_token": "eyJhbGciOiJIUzI1NiIs...", "identifiers": [ { "type": "domain", "value": "bbc.co.uk" }, { "type": "domain", "value": "news.sky.com" }, @@ -454,6 +453,10 @@ The response returns a compact list of **identifiers only** (not full property o } ``` + +The `auth_token` is only returned when the list is created via `create_property_list`. Store it securely - you'll need it to share access with sellers. + + ### Request - Get Metadata Only ```json diff --git a/static/schemas/source/property/create-property-list-response.json b/static/schemas/source/property/create-property-list-response.json index 00ced0d510..2b608088db 100644 --- a/static/schemas/source/property/create-property-list-response.json +++ b/static/schemas/source/property/create-property-list-response.json @@ -9,10 +9,14 @@ "$ref": "/schemas/property/property-list.json", "description": "The created property list" }, + "auth_token": { + "type": "string", + "description": "Token that can be shared with sellers to authorize fetching this list. Store this - it is only returned at creation time." + }, "ext": { "$ref": "/schemas/core/ext.json" } }, - "required": ["list"], + "required": ["list", "auth_token"], "additionalProperties": false } diff --git a/static/schemas/source/property/get-property-list-response.json b/static/schemas/source/property/get-property-list-response.json index ea35b8c930..f2e90754c1 100644 --- a/static/schemas/source/property/get-property-list-response.json +++ b/static/schemas/source/property/get-property-list-response.json @@ -9,10 +9,6 @@ "$ref": "/schemas/property/property-list.json", "description": "The property list metadata (always returned)" }, - "auth_token": { - "type": "string", - "description": "Token that can be shared with sellers to authorize fetching this list" - }, "identifiers": { "type": "array", "description": "Resolved identifiers that passed filters (if resolve=true). Cache these locally for real-time use.", diff --git a/static/schemas/source/property/property-feature-definition.json b/static/schemas/source/property/property-feature-definition.json index 91ca511231..baaf017bb7 100644 --- a/static/schemas/source/property/property-feature-definition.json +++ b/static/schemas/source/property/property-feature-definition.json @@ -66,10 +66,19 @@ }, "additionalProperties": false }, + "methodology_url": { + "type": "string", + "format": "uri", + "description": "URL to documentation explaining how this feature is calculated/measured" + }, + "methodology_version": { + "type": "string", + "description": "Version identifier for the methodology (for audit trails)" + }, "ext": { "$ref": "/schemas/core/ext.json" } }, - "required": ["feature_id", "name", "type"], + "required": ["feature_id", "name", "type", "methodology_url"], "additionalProperties": false } diff --git a/static/schemas/source/property/property-list-changed-webhook.json b/static/schemas/source/property/property-list-changed-webhook.json index 482c325bcc..69b6c8f17f 100644 --- a/static/schemas/source/property/property-list-changed-webhook.json +++ b/static/schemas/source/property/property-list-changed-webhook.json @@ -47,10 +47,14 @@ "format": "date-time", "description": "When the consumer should refresh from the governance agent" }, + "signature": { + "type": "string", + "description": "Cryptographic signature of the webhook payload, signed with the agent's private key. Recipients MUST verify this signature." + }, "ext": { "$ref": "/schemas/core/ext.json" } }, - "required": ["event", "list_id", "resolved_at"], + "required": ["event", "list_id", "resolved_at", "signature"], "additionalProperties": false } diff --git a/static/schemas/source/property/property-list-filters.json b/static/schemas/source/property/property-list-filters.json index d9e6ed5bc2..2e95d6c737 100644 --- a/static/schemas/source/property/property-list-filters.json +++ b/static/schemas/source/property/property-list-filters.json @@ -42,5 +42,6 @@ } } }, + "required": ["countries_all", "channels_any"], "additionalProperties": false } From 7303dd9d734f995d3d709cc2ac20c4a325d9a4c8 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 13:52:07 -0500 Subject: [PATCH 05/19] Add validate_property_delivery task for post-campaign compliance validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces the "what I wanted vs what I got" validation concept: - validate_property_delivery task validates delivery records against property lists - Three statuses: compliant, non_compliant, unknown - Unknown records are valid (detection gaps shouldn't penalize compliance) - Compliance rate excludes unknown impressions from calculation - Batch support up to 10,000 records per request New schemas: - delivery-record.json: Input record with identifier + impressions - validation-result.json: Per-record validation result with status and violations - validate-property-delivery-request.json: Request payload - validate-property-delivery-response.json: Response with summary and per-record results 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/property/specification.mdx | 27 ++- docs/governance/property/tasks/index.mdx | 8 + .../tasks/validate_property_delivery.mdx | 214 ++++++++++++++++++ .../source/property/delivery-record.json | 27 +++ .../validate-property-delivery-request.json | 32 +++ .../validate-property-delivery-response.json | 91 ++++++++ .../source/property/validation-result.json | 51 +++++ 7 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 docs/governance/property/tasks/validate_property_delivery.mdx create mode 100644 static/schemas/source/property/delivery-record.json create mode 100644 static/schemas/source/property/validate-property-delivery-request.json create mode 100644 static/schemas/source/property/validate-property-delivery-response.json create mode 100644 static/schemas/source/property/validation-result.json diff --git a/docs/governance/property/specification.mdx b/docs/governance/property/specification.mdx index 5c636be3e6..c950062acf 100644 --- a/docs/governance/property/specification.mdx +++ b/docs/governance/property/specification.mdx @@ -239,6 +239,29 @@ List all property lists accessible to the authenticated principal. Remove a property list. +### Validation + +#### validate_property_delivery + +Validates delivery records against a property list to determine compliance. Closes the loop between "what I wanted" and "what I got." + +**Use Cases**: +- Post-campaign validation: Verify impressions landed on compliant properties +- Real-time monitoring: Check compliance rate during campaign execution +- Audit trails: Generate compliance reports for regulatory or brand safety reviews + +**Validation Statuses**: +- `compliant`: Identifier is in the resolved property list +- `non_compliant`: Identifier is NOT in the resolved property list +- `unknown`: Identifier type not resolvable by this agent + +**Unknown Records Are Valid**: When a governance agent cannot resolve an identifier (e.g., client-side JavaScript detection didn't fire, or the identifier type is outside the agent's coverage), the record receives `unknown` status. Unknown records are excluded from compliance rate calculations - you cannot penalize for detection gaps. + +**Compliance Rate Calculation**: +``` +compliance_rate = compliant_impressions / (total_impressions - unknown_impressions) +``` + ## Typical Flows ### Property List Flow @@ -396,7 +419,8 @@ Governance agents expose capabilities via `.well-known/agent-card.json`: "get_property_list", "update_property_list", "delete_property_list", - "list_property_lists" + "list_property_lists", + "validate_property_delivery" ], "protocols": ["MCP", "A2A"], "schema_version": "v1" @@ -576,3 +600,4 @@ Media buys can reference governance policies via property list references: - See the [adagents.json Tech Spec](/docs/governance/property/adagents) for property declaration and authorization - See the [list_property_features task reference](/docs/governance/property/tasks/list_property_features) for capability discovery - See the [Property List Management](/docs/governance/property/tasks/property_lists) for CRUD operations and webhooks +- See the [validate_property_delivery task reference](/docs/governance/property/tasks/validate_property_delivery) for post-campaign compliance validation diff --git a/docs/governance/property/tasks/index.mdx b/docs/governance/property/tasks/index.mdx index c6f8f65736..92e015acc8 100644 --- a/docs/governance/property/tasks/index.mdx +++ b/docs/governance/property/tasks/index.mdx @@ -29,6 +29,14 @@ Property governance uses a **stateful** model where all evaluation happens throu See [Property List Management](/docs/governance/property/tasks/property_lists) for complete CRUD documentation. +## Validation + +| Task | Purpose | Response Time | +|------|---------|---------------| +| [validate_property_delivery](/docs/governance/property/tasks/validate_property_delivery) | Validate delivery records against a list | ~1-5s | + +See [validate_property_delivery](/docs/governance/property/tasks/validate_property_delivery) for post-campaign compliance validation. + ## Task Selection Guide ### Creating a Property List diff --git a/docs/governance/property/tasks/validate_property_delivery.mdx b/docs/governance/property/tasks/validate_property_delivery.mdx new file mode 100644 index 0000000000..4d7da78835 --- /dev/null +++ b/docs/governance/property/tasks/validate_property_delivery.mdx @@ -0,0 +1,214 @@ +--- +sidebar_position: 4 +title: validate_property_delivery +--- + +# validate_property_delivery + + +**AdCP 3.0 Proposal** - This task is under development for AdCP 3.0. + + +Validates delivery records against a property list to determine compliance. Answers the question: "Did my impressions land on properties in my list?" + +## Use Cases + +- **Post-campaign validation**: Verify that impressions were delivered to compliant properties +- **Real-time monitoring**: Check compliance rate during campaign execution +- **Audit trails**: Generate compliance reports for regulatory or brand safety reviews + +## Request + +```json +{ + "$schema": "/schemas/property/validate-property-delivery-request.json", + "list_id": "pl_abc123", + "records": [ + { + "identifier": { "type": "domain", "value": "www.nytimes.com" }, + "impressions": 103 + }, + { + "identifier": { "type": "domain", "value": "sketchy-site.example" }, + "impressions": 47 + }, + { + "identifier": { "type": "android_package", "value": "com.unknown.app" }, + "impressions": 25 + } + ], + "include_compliant": false +} +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `list_id` | string | Yes | ID of the property list to validate against | +| `records` | array | Yes | Delivery records to validate (1-10,000 records) | +| `records[].identifier` | object | Yes | Property identifier (`type` and `value`) | +| `records[].impressions` | integer | Yes | Number of impressions delivered | +| `records[].record_id` | string | No | Client-provided ID for correlation | +| `include_compliant` | boolean | No | Include compliant records in results (default: false) | + +## Response + +```json +{ + "$schema": "/schemas/property/validate-property-delivery-response.json", + "list_id": "pl_abc123", + "summary": { + "total_records": 3, + "total_impressions": 175, + "compliant_records": 1, + "compliant_impressions": 103, + "non_compliant_records": 1, + "non_compliant_impressions": 47, + "unknown_records": 1, + "unknown_impressions": 25, + "compliance_rate": 0.687 + }, + "results": [ + { + "identifier": { "type": "domain", "value": "sketchy-site.example" }, + "status": "non_compliant", + "impressions": 47, + "violations": [ + { + "code": "not_in_list", + "message": "Identifier not found in resolved property list" + } + ] + }, + { + "identifier": { "type": "android_package", "value": "com.unknown.app" }, + "status": "unknown", + "impressions": 25 + } + ], + "validated_at": "2026-01-04T19:00:00Z", + "list_resolved_at": "2026-01-04T12:00:00Z" +} +``` + +### Response Fields + +| Field | Type | Description | +|-------|------|-------------| +| `list_id` | string | ID of the property list validated against | +| `summary` | object | Aggregate validation statistics | +| `summary.compliance_rate` | number | Compliant / (total - unknown) impressions | +| `results` | array | Per-record validation results | +| `validated_at` | datetime | When validation was performed | +| `list_resolved_at` | datetime | Resolution timestamp of the property list used | + +### Validation Statuses + +| Status | Meaning | Included in compliance_rate? | +|--------|---------|------------------------------| +| `compliant` | Identifier is in the resolved property list | Yes (numerator) | +| `non_compliant` | Identifier is NOT in the resolved property list | Yes (denominator only) | +| `unknown` | Identifier type not resolvable by this agent | No (excluded) | + +## Understanding Unknown Status + +Records receive `unknown` status when the governance agent cannot resolve the identifier type. This commonly occurs when: + +- **Client-side detection limitations**: JavaScript-based property detection may not identify all placements +- **Identifier type mismatch**: The agent specializes in domains but receives app store IDs +- **New properties**: Recently added properties not yet in the agent's database + +**Unknown records are valid** - you cannot penalize for detection gaps. They are excluded from the `compliance_rate` calculation. + +## Compliance Rate Calculation + +``` +compliance_rate = compliant_impressions / (total_impressions - unknown_impressions) +``` + +In the example above: +- Compliant impressions: 103 +- Non-compliant impressions: 47 +- Unknown impressions: 25 (excluded) +- Compliance rate: 103 / (175 - 25) = 103 / 150 = 0.687 (68.7%) + +## Violation Codes + +| Code | Description | +|------|-------------| +| `not_in_list` | Identifier not found in the resolved property list | +| `excluded` | Identifier explicitly excluded via `exclude_identifiers` filter | +| `country_mismatch` | Property not compliant in required countries | +| `channel_mismatch` | Property doesn't support required channels | +| `feature_failed` | Property failed a `feature_requirements` filter | + +## Best Practices + +### Batch Validation + +For large-scale validation, batch records up to the 10,000 limit: + +```python +def validate_delivery_batch(records, list_id, governance_agent): + """Validate delivery records in batches.""" + batch_size = 10000 + all_results = [] + + for i in range(0, len(records), batch_size): + batch = records[i:i + batch_size] + response = governance_agent.validate_property_delivery( + list_id=list_id, + records=batch + ) + all_results.extend(response.results) + + return all_results +``` + +### Sampling Strategy + +For real-time monitoring during campaign execution, validate a statistical sample rather than all records: + +```python +import random + +def sample_and_validate(records, sample_size=1000): + """Validate a random sample for real-time monitoring.""" + sample = random.sample(records, min(sample_size, len(records))) + return governance_agent.validate_property_delivery( + list_id=list_id, + records=sample + ) +``` + +### Handling Unknown Records + +Track unknown rates separately to identify detection gaps: + +```python +def analyze_validation(response): + """Analyze validation results with unknown handling.""" + summary = response.summary + + # Core compliance metric + compliance_rate = summary.compliance_rate + + # Detection quality metric + unknown_rate = summary.unknown_impressions / summary.total_impressions + + if unknown_rate > 0.1: + print(f"Warning: {unknown_rate:.1%} of impressions unresolvable") + + return { + "compliance_rate": compliance_rate, + "unknown_rate": unknown_rate, + "non_compliant_impressions": summary.non_compliant_impressions + } +``` + +## Related Tasks + +- [create_property_list](/docs/governance/property/tasks/property_lists#create_property_list) - Create the list to validate against +- [get_property_list](/docs/governance/property/tasks/property_lists#get_property_list) - Retrieve current list membership +- [list_property_features](/docs/governance/property/tasks/list_property_features) - Discover available filter features diff --git a/static/schemas/source/property/delivery-record.json b/static/schemas/source/property/delivery-record.json new file mode 100644 index 0000000000..344f25eda0 --- /dev/null +++ b/static/schemas/source/property/delivery-record.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/delivery-record.json", + "title": "Delivery Record", + "description": "A single delivery record representing impressions served to a property identifier. Used as input to validate_property_delivery.", + "type": "object", + "properties": { + "identifier": { + "$ref": "/schemas/core/identifier.json", + "description": "The property identifier where impressions were delivered" + }, + "impressions": { + "type": "integer", + "minimum": 0, + "description": "Number of impressions delivered to this identifier" + }, + "record_id": { + "type": "string", + "description": "Optional client-provided ID for correlating results back to source data" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["identifier", "impressions"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/validate-property-delivery-request.json b/static/schemas/source/property/validate-property-delivery-request.json new file mode 100644 index 0000000000..491e8c3ac8 --- /dev/null +++ b/static/schemas/source/property/validate-property-delivery-request.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/validate-property-delivery-request.json", + "title": "Validate Property Delivery Request", + "description": "Request payload for validate_property_delivery task. Validates delivery records against a property list to determine compliance.", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list to validate against" + }, + "records": { + "type": "array", + "description": "Delivery records to validate. Each record represents impressions delivered to a property identifier.", + "items": { + "$ref": "/schemas/property/delivery-record.json" + }, + "minItems": 1, + "maxItems": 10000 + }, + "include_compliant": { + "type": "boolean", + "default": false, + "description": "Include compliant records in results (default: only return non_compliant and unknown)" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list_id", "records"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/validate-property-delivery-response.json b/static/schemas/source/property/validate-property-delivery-response.json new file mode 100644 index 0000000000..f18d76f61f --- /dev/null +++ b/static/schemas/source/property/validate-property-delivery-response.json @@ -0,0 +1,91 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/validate-property-delivery-response.json", + "title": "Validate Property Delivery Response", + "description": "Response payload for validate_property_delivery task. Returns aggregate compliance statistics and per-record validation results.", + "type": "object", + "properties": { + "list_id": { + "type": "string", + "description": "ID of the property list validated against" + }, + "summary": { + "type": "object", + "description": "Aggregate validation statistics", + "properties": { + "total_records": { + "type": "integer", + "description": "Total number of records validated" + }, + "total_impressions": { + "type": "integer", + "description": "Total impressions across all records" + }, + "compliant_records": { + "type": "integer", + "description": "Number of records with compliant status" + }, + "compliant_impressions": { + "type": "integer", + "description": "Impressions from compliant records" + }, + "non_compliant_records": { + "type": "integer", + "description": "Number of records with non_compliant status" + }, + "non_compliant_impressions": { + "type": "integer", + "description": "Impressions from non_compliant records" + }, + "unknown_records": { + "type": "integer", + "description": "Number of records with unknown status (identifier not resolvable)" + }, + "unknown_impressions": { + "type": "integer", + "description": "Impressions from unknown records" + }, + "compliance_rate": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Compliant impressions divided by (total - unknown) impressions. Unknown impressions are excluded from the rate calculation." + } + }, + "required": [ + "total_records", + "total_impressions", + "compliant_records", + "compliant_impressions", + "non_compliant_records", + "non_compliant_impressions", + "unknown_records", + "unknown_impressions", + "compliance_rate" + ], + "additionalProperties": false + }, + "results": { + "type": "array", + "description": "Per-record validation results. By default only includes non_compliant and unknown records. Set include_compliant=true to include all records.", + "items": { + "$ref": "/schemas/property/validation-result.json" + } + }, + "validated_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp when validation was performed" + }, + "list_resolved_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of the property list resolution used for validation" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["list_id", "summary", "results", "validated_at"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/validation-result.json b/static/schemas/source/property/validation-result.json new file mode 100644 index 0000000000..f10b1a2f12 --- /dev/null +++ b/static/schemas/source/property/validation-result.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/validation-result.json", + "title": "Validation Result", + "description": "Result of validating a single delivery record against a property list.", + "type": "object", + "properties": { + "identifier": { + "$ref": "/schemas/core/identifier.json", + "description": "The identifier that was validated" + }, + "record_id": { + "type": "string", + "description": "Client-provided ID from the delivery record (if provided)" + }, + "status": { + "type": "string", + "enum": ["compliant", "non_compliant", "unknown"], + "description": "Validation status: compliant (in list), non_compliant (not in list), unknown (identifier type not resolvable by this governance agent)" + }, + "impressions": { + "type": "integer", + "minimum": 0, + "description": "Number of impressions from this record" + }, + "violations": { + "type": "array", + "description": "Specific violations found (only present for non_compliant records)", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Machine-readable violation code" + }, + "message": { + "type": "string", + "description": "Human-readable violation description" + } + }, + "required": ["code", "message"], + "additionalProperties": false + } + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["identifier", "status", "impressions"], + "additionalProperties": false +} From 1eeaac990850cb462e3afa03dc3c1ef5637bdc8d Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 13:57:03 -0500 Subject: [PATCH 06/19] Add supply path authorization to validate_property_delivery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends validate_property_delivery to validate sales agent authorization: - Add optional sales_agent_url to delivery records - Check if sales agent is listed in publisher's adagents.json - Return authorization status (authorized/unauthorized/unknown) per record - Add authorization_summary with aggregate stats and authorization_rate - Two independent checks: property compliance + supply path authorization New schema: - authorization-result.json: Per-record authorization validation result Authorization uses same "unknown excludes from rate" pattern as property compliance - you cannot penalize for detection gaps or unreachable files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/property/specification.mdx | 17 ++- .../tasks/validate_property_delivery.mdx | 109 +++++++++++++++++- .../source/property/authorization-result.json | 41 +++++++ .../source/property/delivery-record.json | 5 + .../validate-property-delivery-response.json | 56 +++++++++ .../source/property/validation-result.json | 4 + 6 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 static/schemas/source/property/authorization-result.json diff --git a/docs/governance/property/specification.mdx b/docs/governance/property/specification.mdx index c950062acf..d53dbc3a4c 100644 --- a/docs/governance/property/specification.mdx +++ b/docs/governance/property/specification.mdx @@ -245,21 +245,32 @@ Remove a property list. Validates delivery records against a property list to determine compliance. Closes the loop between "what I wanted" and "what I got." +Performs two independent validations: +1. **Property compliance**: Is the identifier in the resolved property list? +2. **Supply path authorization**: Was the sales agent authorized to sell that property? (optional, requires `sales_agent_url`) + **Use Cases**: - Post-campaign validation: Verify impressions landed on compliant properties +- Supply path verification: Confirm sales agents were authorized by publishers - Real-time monitoring: Check compliance rate during campaign execution - Audit trails: Generate compliance reports for regulatory or brand safety reviews -**Validation Statuses**: +**Property Validation Statuses**: - `compliant`: Identifier is in the resolved property list - `non_compliant`: Identifier is NOT in the resolved property list - `unknown`: Identifier type not resolvable by this agent -**Unknown Records Are Valid**: When a governance agent cannot resolve an identifier (e.g., client-side JavaScript detection didn't fire, or the identifier type is outside the agent's coverage), the record receives `unknown` status. Unknown records are excluded from compliance rate calculations - you cannot penalize for detection gaps. +**Authorization Validation Statuses** (when `sales_agent_url` provided): +- `authorized`: Sales agent is listed in publisher's adagents.json +- `unauthorized`: Sales agent is NOT in publisher's authorized_agents list +- `unknown`: Could not fetch or parse adagents.json + +**Unknown Records Are Valid**: For both property and authorization checks, unknown records are excluded from rate calculations. You cannot penalize for detection gaps or unreachable publisher files. -**Compliance Rate Calculation**: +**Rate Calculations**: ``` compliance_rate = compliant_impressions / (total_impressions - unknown_impressions) +authorization_rate = authorized_impressions / (checked_impressions - unknown_impressions) ``` ## Typical Flows diff --git a/docs/governance/property/tasks/validate_property_delivery.mdx b/docs/governance/property/tasks/validate_property_delivery.mdx index 4d7da78835..b8cbddd5af 100644 --- a/docs/governance/property/tasks/validate_property_delivery.mdx +++ b/docs/governance/property/tasks/validate_property_delivery.mdx @@ -9,11 +9,14 @@ title: validate_property_delivery **AdCP 3.0 Proposal** - This task is under development for AdCP 3.0. -Validates delivery records against a property list to determine compliance. Answers the question: "Did my impressions land on properties in my list?" +Validates delivery records against a property list to determine compliance. Answers two questions: +1. **Property compliance**: Did my impressions land on properties in my list? +2. **Supply path authorization**: Was the sales agent authorized to sell that inventory? ## Use Cases - **Post-campaign validation**: Verify that impressions were delivered to compliant properties +- **Supply path verification**: Confirm sales agents were authorized by publishers - **Real-time monitoring**: Check compliance rate during campaign execution - **Audit trails**: Generate compliance reports for regulatory or brand safety reviews @@ -50,6 +53,7 @@ Validates delivery records against a property list to determine compliance. Answ | `records[].identifier` | object | Yes | Property identifier (`type` and `value`) | | `records[].impressions` | integer | Yes | Number of impressions delivered | | `records[].record_id` | string | No | Client-provided ID for correlation | +| `records[].sales_agent_url` | string | No | Sales agent URL to validate authorization against adagents.json | | `include_compliant` | boolean | No | Include compliant records in results (default: false) | ## Response @@ -143,6 +147,109 @@ In the example above: | `channel_mismatch` | Property doesn't support required channels | | `feature_failed` | Property failed a `feature_requirements` filter | +## Supply Path Authorization + +When `sales_agent_url` is provided in delivery records, the governance agent validates that the sales agent is authorized to sell the property by checking the publisher's `adagents.json`. + +### Request with Authorization + +```json +{ + "list_id": "pl_abc123", + "records": [ + { + "identifier": { "type": "domain", "value": "www.nytimes.com" }, + "impressions": 103, + "sales_agent_url": "https://legitimate-ssp.example.com" + }, + { + "identifier": { "type": "domain", "value": "www.nytimes.com" }, + "impressions": 50, + "sales_agent_url": "https://unauthorized-reseller.example.com" + } + ] +} +``` + +### Response with Authorization + +When authorization is validated, each result includes an `authorization` field: + +```json +{ + "list_id": "pl_abc123", + "summary": { + "total_records": 2, + "total_impressions": 153, + "compliant_records": 2, + "compliant_impressions": 153, + "non_compliant_records": 0, + "non_compliant_impressions": 0, + "unknown_records": 0, + "unknown_impressions": 0, + "compliance_rate": 1.0 + }, + "authorization_summary": { + "records_checked": 2, + "impressions_checked": 153, + "authorized_records": 1, + "authorized_impressions": 103, + "unauthorized_records": 1, + "unauthorized_impressions": 50, + "unknown_records": 0, + "unknown_impressions": 0, + "authorization_rate": 0.673 + }, + "results": [ + { + "identifier": { "type": "domain", "value": "www.nytimes.com" }, + "status": "compliant", + "impressions": 50, + "authorization": { + "status": "unauthorized", + "publisher_domain": "nytimes.com", + "sales_agent_url": "https://unauthorized-reseller.example.com", + "violation": { + "code": "agent_not_authorized", + "message": "Sales agent not listed in nytimes.com/.well-known/adagents.json" + } + } + } + ], + "validated_at": "2026-01-04T19:00:00Z" +} +``` + +### Authorization Statuses + +| Status | Meaning | Included in authorization_rate? | +|--------|---------|--------------------------------| +| `authorized` | Sales agent is listed in publisher's adagents.json | Yes (numerator) | +| `unauthorized` | Sales agent is NOT listed in publisher's adagents.json | Yes (denominator only) | +| `unknown` | Could not fetch or parse adagents.json | No (excluded) | + +### Authorization Violation Codes + +| Code | Description | +|------|-------------| +| `agent_not_authorized` | Sales agent URL not found in publisher's authorized_agents list | +| `adagents_not_found` | Publisher's adagents.json could not be fetched (404, timeout, etc.) | +| `adagents_invalid` | Publisher's adagents.json exists but is malformed | +| `property_not_declared` | Property identifier not declared in publisher's adagents.json | + +### Two Independent Checks + +Property compliance and authorization are **independent checks**. A record can be: + +| Property Status | Authorization Status | Meaning | +|-----------------|---------------------|---------| +| compliant | authorized | Fully valid - property in list, sold by authorized agent | +| compliant | unauthorized | Property is approved but sold by unauthorized reseller | +| non_compliant | authorized | Authorized agent sold property outside your list | +| non_compliant | unauthorized | Neither property nor agent validated | + +Both checks use the same "unknown excludes from rate" pattern - you cannot penalize for detection gaps. + ## Best Practices ### Batch Validation diff --git a/static/schemas/source/property/authorization-result.json b/static/schemas/source/property/authorization-result.json new file mode 100644 index 0000000000..48187649fc --- /dev/null +++ b/static/schemas/source/property/authorization-result.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/authorization-result.json", + "title": "Authorization Result", + "description": "Result of validating sales agent authorization for a property. Checks if the sales agent is listed in the property's adagents.json.", + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": ["authorized", "unauthorized", "unknown"], + "description": "Authorization status: authorized (agent in adagents.json), unauthorized (agent not in adagents.json), unknown (could not fetch or parse adagents.json)" + }, + "publisher_domain": { + "type": "string", + "description": "The publisher domain where adagents.json was checked" + }, + "sales_agent_url": { + "type": "string", + "format": "uri", + "description": "The sales agent URL that was validated" + }, + "violation": { + "type": "object", + "description": "Details about the authorization failure (only present for unauthorized status)", + "properties": { + "code": { + "type": "string", + "description": "Machine-readable violation code" + }, + "message": { + "type": "string", + "description": "Human-readable violation description" + } + }, + "required": ["code", "message"], + "additionalProperties": false + } + }, + "required": ["status"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/delivery-record.json b/static/schemas/source/property/delivery-record.json index 344f25eda0..29e6bc17a7 100644 --- a/static/schemas/source/property/delivery-record.json +++ b/static/schemas/source/property/delivery-record.json @@ -18,6 +18,11 @@ "type": "string", "description": "Optional client-provided ID for correlating results back to source data" }, + "sales_agent_url": { + "type": "string", + "format": "uri", + "description": "URL of the sales agent that sold this inventory. If provided, authorization is validated against the property's adagents.json." + }, "ext": { "$ref": "/schemas/core/ext.json" } diff --git a/static/schemas/source/property/validate-property-delivery-response.json b/static/schemas/source/property/validate-property-delivery-response.json index f18d76f61f..d7d94d4962 100644 --- a/static/schemas/source/property/validate-property-delivery-response.json +++ b/static/schemas/source/property/validate-property-delivery-response.json @@ -65,6 +65,62 @@ ], "additionalProperties": false }, + "authorization_summary": { + "type": "object", + "description": "Aggregate authorization statistics. Only present if any records included sales_agent_url.", + "properties": { + "records_checked": { + "type": "integer", + "description": "Number of records with sales_agent_url provided" + }, + "impressions_checked": { + "type": "integer", + "description": "Total impressions from records with sales_agent_url" + }, + "authorized_records": { + "type": "integer", + "description": "Number of records where sales agent was authorized" + }, + "authorized_impressions": { + "type": "integer", + "description": "Impressions from authorized records" + }, + "unauthorized_records": { + "type": "integer", + "description": "Number of records where sales agent was NOT authorized" + }, + "unauthorized_impressions": { + "type": "integer", + "description": "Impressions from unauthorized records" + }, + "unknown_records": { + "type": "integer", + "description": "Number of records where authorization could not be determined (adagents.json unavailable)" + }, + "unknown_impressions": { + "type": "integer", + "description": "Impressions from records where authorization could not be determined" + }, + "authorization_rate": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Authorized impressions divided by (checked - unknown) impressions. Unknown impressions are excluded from the rate calculation." + } + }, + "required": [ + "records_checked", + "impressions_checked", + "authorized_records", + "authorized_impressions", + "unauthorized_records", + "unauthorized_impressions", + "unknown_records", + "unknown_impressions", + "authorization_rate" + ], + "additionalProperties": false + }, "results": { "type": "array", "description": "Per-record validation results. By default only includes non_compliant and unknown records. Set include_compliant=true to include all records.", diff --git a/static/schemas/source/property/validation-result.json b/static/schemas/source/property/validation-result.json index f10b1a2f12..fa4bcaeb8b 100644 --- a/static/schemas/source/property/validation-result.json +++ b/static/schemas/source/property/validation-result.json @@ -42,6 +42,10 @@ "additionalProperties": false } }, + "authorization": { + "$ref": "/schemas/property/authorization-result.json", + "description": "Authorization validation result (only present if sales_agent_url was provided in the delivery record)" + }, "ext": { "$ref": "/schemas/core/ext.json" } From 9ebfa0ae5c6e7a2bc86f23ffd766341402a1cfa5 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 14:01:35 -0500 Subject: [PATCH 07/19] Simplify validation response: raw counts + optional feature details MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Remove compliance_rate and authorization_rate from response schemas - Return raw counts only (1.2M verified, 0.8M unknown, 0.3M invalid) - Consumers calculate rates as needed, excluding unknowns from denominator - Add optional feature_id and requirement to violation objects - For feature_failed violations, shows which feature and what threshold failed - Helps debugging why a property was marked non-compliant 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/property/specification.mdx | 8 +-- .../tasks/validate_property_delivery.mdx | 72 ++++++++++++++----- .../validate-property-delivery-response.json | 18 +---- .../source/property/validation-result.json | 24 +++++++ 4 files changed, 82 insertions(+), 40 deletions(-) diff --git a/docs/governance/property/specification.mdx b/docs/governance/property/specification.mdx index d53dbc3a4c..e39bad2108 100644 --- a/docs/governance/property/specification.mdx +++ b/docs/governance/property/specification.mdx @@ -265,13 +265,9 @@ Performs two independent validations: - `unauthorized`: Sales agent is NOT in publisher's authorized_agents list - `unknown`: Could not fetch or parse adagents.json -**Unknown Records Are Valid**: For both property and authorization checks, unknown records are excluded from rate calculations. You cannot penalize for detection gaps or unreachable publisher files. +**Unknown Records Are Valid**: For both property and authorization checks, unknown records should be excluded when calculating rates. You cannot penalize for detection gaps or unreachable publisher files. -**Rate Calculations**: -``` -compliance_rate = compliant_impressions / (total_impressions - unknown_impressions) -authorization_rate = authorized_impressions / (checked_impressions - unknown_impressions) -``` +**Response Format**: The response returns raw counts (compliant, non_compliant, unknown impressions). Consumers calculate rates as needed, typically excluding unknown impressions from the denominator. ## Typical Flows diff --git a/docs/governance/property/tasks/validate_property_delivery.mdx b/docs/governance/property/tasks/validate_property_delivery.mdx index b8cbddd5af..f0c6150fbc 100644 --- a/docs/governance/property/tasks/validate_property_delivery.mdx +++ b/docs/governance/property/tasks/validate_property_delivery.mdx @@ -70,8 +70,7 @@ Validates delivery records against a property list to determine compliance. Answ "non_compliant_records": 1, "non_compliant_impressions": 47, "unknown_records": 1, - "unknown_impressions": 25, - "compliance_rate": 0.687 + "unknown_impressions": 25 }, "results": [ { @@ -101,19 +100,30 @@ Validates delivery records against a property list to determine compliance. Answ | Field | Type | Description | |-------|------|-------------| | `list_id` | string | ID of the property list validated against | -| `summary` | object | Aggregate validation statistics | -| `summary.compliance_rate` | number | Compliant / (total - unknown) impressions | +| `summary` | object | Raw counts for property compliance validation | | `results` | array | Per-record validation results | | `validated_at` | datetime | When validation was performed | | `list_resolved_at` | datetime | Resolution timestamp of the property list used | +### Summary Fields + +The summary provides raw counts - consumers calculate rates as needed: + +| Field | Description | +|-------|-------------| +| `total_records` | Total records validated | +| `total_impressions` | Total impressions across all records | +| `compliant_records` / `compliant_impressions` | Records/impressions in the property list | +| `non_compliant_records` / `non_compliant_impressions` | Records/impressions NOT in the property list | +| `unknown_records` / `unknown_impressions` | Records/impressions that couldn't be resolved | + ### Validation Statuses -| Status | Meaning | Included in compliance_rate? | -|--------|---------|------------------------------| -| `compliant` | Identifier is in the resolved property list | Yes (numerator) | -| `non_compliant` | Identifier is NOT in the resolved property list | Yes (denominator only) | -| `unknown` | Identifier type not resolvable by this agent | No (excluded) | +| Status | Meaning | +|--------|---------| +| `compliant` | Identifier is in the resolved property list | +| `non_compliant` | Identifier is NOT in the resolved property list | +| `unknown` | Identifier type not resolvable by this agent | ## Understanding Unknown Status @@ -123,19 +133,23 @@ Records receive `unknown` status when the governance agent cannot resolve the id - **Identifier type mismatch**: The agent specializes in domains but receives app store IDs - **New properties**: Recently added properties not yet in the agent's database -**Unknown records are valid** - you cannot penalize for detection gaps. They are excluded from the `compliance_rate` calculation. +**Unknown records are valid** - you cannot penalize for detection gaps. -## Compliance Rate Calculation +## Calculating Rates -``` -compliance_rate = compliant_impressions / (total_impressions - unknown_impressions) +The response returns raw counts. Calculate rates as needed: + +```python +# Compliance rate (exclude unknowns from denominator) +verifiable = summary.total_impressions - summary.unknown_impressions +compliance_rate = summary.compliant_impressions / verifiable if verifiable > 0 else None ``` In the example above: - Compliant impressions: 103 - Non-compliant impressions: 47 - Unknown impressions: 25 (excluded) -- Compliance rate: 103 / (175 - 25) = 103 / 150 = 0.687 (68.7%) +- Compliance rate: 103 / (175 - 25) = 103 / 150 = 68.7% ## Violation Codes @@ -147,6 +161,30 @@ In the example above: | `channel_mismatch` | Property doesn't support required channels | | `feature_failed` | Property failed a `feature_requirements` filter | +### Feature Violation Details + +When a property fails a feature requirement, the violation includes the feature details: + +```json +{ + "identifier": { "type": "domain", "value": "low-quality-site.example" }, + "status": "non_compliant", + "impressions": 150, + "violations": [ + { + "code": "feature_failed", + "message": "Property failed consent_quality requirement (min: 85)", + "feature_id": "consent_quality", + "requirement": { + "min_value": 85 + } + } + ] +} +``` + +The `feature_id` and `requirement` fields are optional - they're included when the violation is due to a feature requirement filter. + ## Supply Path Authorization When `sales_agent_url` is provided in delivery records, the governance agent validates that the sales agent is authorized to sell the property by checking the publisher's `adagents.json`. @@ -186,8 +224,7 @@ When authorization is validated, each result includes an `authorization` field: "non_compliant_records": 0, "non_compliant_impressions": 0, "unknown_records": 0, - "unknown_impressions": 0, - "compliance_rate": 1.0 + "unknown_impressions": 0 }, "authorization_summary": { "records_checked": 2, @@ -197,8 +234,7 @@ When authorization is validated, each result includes an `authorization` field: "unauthorized_records": 1, "unauthorized_impressions": 50, "unknown_records": 0, - "unknown_impressions": 0, - "authorization_rate": 0.673 + "unknown_impressions": 0 }, "results": [ { diff --git a/static/schemas/source/property/validate-property-delivery-response.json b/static/schemas/source/property/validate-property-delivery-response.json index d7d94d4962..f5c9b74af4 100644 --- a/static/schemas/source/property/validate-property-delivery-response.json +++ b/static/schemas/source/property/validate-property-delivery-response.json @@ -44,12 +44,6 @@ "unknown_impressions": { "type": "integer", "description": "Impressions from unknown records" - }, - "compliance_rate": { - "type": "number", - "minimum": 0, - "maximum": 1, - "description": "Compliant impressions divided by (total - unknown) impressions. Unknown impressions are excluded from the rate calculation." } }, "required": [ @@ -60,8 +54,7 @@ "non_compliant_records", "non_compliant_impressions", "unknown_records", - "unknown_impressions", - "compliance_rate" + "unknown_impressions" ], "additionalProperties": false }, @@ -100,12 +93,6 @@ "unknown_impressions": { "type": "integer", "description": "Impressions from records where authorization could not be determined" - }, - "authorization_rate": { - "type": "number", - "minimum": 0, - "maximum": 1, - "description": "Authorized impressions divided by (checked - unknown) impressions. Unknown impressions are excluded from the rate calculation." } }, "required": [ @@ -116,8 +103,7 @@ "unauthorized_records", "unauthorized_impressions", "unknown_records", - "unknown_impressions", - "authorization_rate" + "unknown_impressions" ], "additionalProperties": false }, diff --git a/static/schemas/source/property/validation-result.json b/static/schemas/source/property/validation-result.json index fa4bcaeb8b..83ef11721e 100644 --- a/static/schemas/source/property/validation-result.json +++ b/static/schemas/source/property/validation-result.json @@ -36,6 +36,30 @@ "message": { "type": "string", "description": "Human-readable violation description" + }, + "feature_id": { + "type": "string", + "description": "ID of the feature that caused the violation (only present for feature_failed violations)" + }, + "requirement": { + "type": "object", + "description": "The feature requirement that was not met (only present for feature_failed violations)", + "properties": { + "min_value": { + "type": "number", + "description": "Minimum value that was required" + }, + "max_value": { + "type": "number", + "description": "Maximum value that was allowed" + }, + "allowed_values": { + "type": "array", + "description": "Values that would have been acceptable", + "items": {} + } + }, + "additionalProperties": false } }, "required": ["code", "message"], From e0aba5dd6854b6742813db6ceeac8c4deea79841 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 14:14:08 -0500 Subject: [PATCH 08/19] Split unknown into unmodeled/unidentified, add optional aggregate metrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Split "unknown" status into two distinct statuses: - unmodeled: identifier recognized but agent has no data (e.g., property too new) - unidentified: identifier type not resolvable (e.g., detection failed) - Add optional aggregate field for computed metrics: - score, grade, label fields for agent-specific measurements - methodology_url for transparency - Update summary to track both unmodeled and unidentified separately - Update documentation with new statuses and aggregate concept Inspired by Scope3's inventory_coverage (modeled/unmodeled) pattern. The distinction helps identify whether gaps are in data coverage vs identifier resolution. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/property/specification.mdx | 7 +- .../tasks/validate_property_delivery.mdx | 83 ++++++++++++++----- .../validate-property-delivery-response.json | 46 ++++++++-- .../source/property/validation-result.json | 4 +- 4 files changed, 110 insertions(+), 30 deletions(-) diff --git a/docs/governance/property/specification.mdx b/docs/governance/property/specification.mdx index e39bad2108..df7385773b 100644 --- a/docs/governance/property/specification.mdx +++ b/docs/governance/property/specification.mdx @@ -258,16 +258,17 @@ Performs two independent validations: **Property Validation Statuses**: - `compliant`: Identifier is in the resolved property list - `non_compliant`: Identifier is NOT in the resolved property list -- `unknown`: Identifier type not resolvable by this agent +- `unmodeled`: Identifier recognized but governance agent has no data for it (e.g., property too new) +- `unidentified`: Identifier type not resolvable by this agent (e.g., detection failed, unsupported type) **Authorization Validation Statuses** (when `sales_agent_url` provided): - `authorized`: Sales agent is listed in publisher's adagents.json - `unauthorized`: Sales agent is NOT in publisher's authorized_agents list - `unknown`: Could not fetch or parse adagents.json -**Unknown Records Are Valid**: For both property and authorization checks, unknown records should be excluded when calculating rates. You cannot penalize for detection gaps or unreachable publisher files. +**Unverifiable Records**: Both `unmodeled` and `unidentified` records should be excluded when calculating compliance rates - you cannot penalize for detection gaps or coverage limitations. The distinction helps identify whether the gap is in the agent's data coverage vs the identifier resolution. -**Response Format**: The response returns raw counts (compliant, non_compliant, unknown impressions). Consumers calculate rates as needed, typically excluding unknown impressions from the denominator. +**Response Format**: The response returns raw counts (compliant, non_compliant, unmodeled, unidentified impressions). Consumers calculate rates as needed. Governance agents may optionally include an `aggregate` field with computed metrics (score, grade, label) - the format and meaning are agent-specific. ## Typical Flows diff --git a/docs/governance/property/tasks/validate_property_delivery.mdx b/docs/governance/property/tasks/validate_property_delivery.mdx index f0c6150fbc..0b0a8801d3 100644 --- a/docs/governance/property/tasks/validate_property_delivery.mdx +++ b/docs/governance/property/tasks/validate_property_delivery.mdx @@ -63,14 +63,22 @@ Validates delivery records against a property list to determine compliance. Answ "$schema": "/schemas/property/validate-property-delivery-response.json", "list_id": "pl_abc123", "summary": { - "total_records": 3, - "total_impressions": 175, + "total_records": 4, + "total_impressions": 200, "compliant_records": 1, "compliant_impressions": 103, "non_compliant_records": 1, "non_compliant_impressions": 47, - "unknown_records": 1, - "unknown_impressions": 25 + "unmodeled_records": 1, + "unmodeled_impressions": 25, + "unidentified_records": 1, + "unidentified_impressions": 25 + }, + "aggregate": { + "score": 68.7, + "grade": "C+", + "label": "68.7% compliant", + "methodology_url": "https://governance.example.com/methodology/compliance-scoring" }, "results": [ { @@ -84,9 +92,14 @@ Validates delivery records against a property list to determine compliance. Answ } ] }, + { + "identifier": { "type": "domain", "value": "new-site.example" }, + "status": "unmodeled", + "impressions": 25 + }, { "identifier": { "type": "android_package", "value": "com.unknown.app" }, - "status": "unknown", + "status": "unidentified", "impressions": 25 } ], @@ -101,6 +114,7 @@ Validates delivery records against a property list to determine compliance. Answ |-------|------|-------------| | `list_id` | string | ID of the property list validated against | | `summary` | object | Raw counts for property compliance validation | +| `aggregate` | object | Optional computed metrics from the governance agent | | `results` | array | Per-record validation results | | `validated_at` | datetime | When validation was performed | | `list_resolved_at` | datetime | Resolution timestamp of the property list used | @@ -115,7 +129,8 @@ The summary provides raw counts - consumers calculate rates as needed: | `total_impressions` | Total impressions across all records | | `compliant_records` / `compliant_impressions` | Records/impressions in the property list | | `non_compliant_records` / `non_compliant_impressions` | Records/impressions NOT in the property list | -| `unknown_records` / `unknown_impressions` | Records/impressions that couldn't be resolved | +| `unmodeled_records` / `unmodeled_impressions` | Identifier recognized but no data available | +| `unidentified_records` / `unidentified_impressions` | Identifier type not resolvable | ### Validation Statuses @@ -123,33 +138,63 @@ The summary provides raw counts - consumers calculate rates as needed: |--------|---------| | `compliant` | Identifier is in the resolved property list | | `non_compliant` | Identifier is NOT in the resolved property list | -| `unknown` | Identifier type not resolvable by this agent | +| `unmodeled` | Identifier recognized but governance agent has no data for it | +| `unidentified` | Identifier type not resolvable by this agent | + +## Understanding Unmodeled vs Unidentified + +These two statuses distinguish different types of "unknown" scenarios: + +**`unmodeled`** - The governance agent recognized the identifier (e.g., it's a valid domain) but doesn't have data for that specific property. This happens when: +- A property is too new to be in the agent's database +- The property exists but hasn't been scored/evaluated yet +- The agent's coverage doesn't include that property category + +**`unidentified`** - The governance agent couldn't recognize the identifier at all. This happens when: +- Client-side detection failed to capture the property +- The identifier type isn't supported (e.g., agent handles domains but received an app ID) +- The identifier value is malformed or invalid -## Understanding Unknown Status +Both statuses should be excluded from compliance rate calculations - you cannot penalize for detection or coverage gaps. -Records receive `unknown` status when the governance agent cannot resolve the identifier type. This commonly occurs when: +## Optional Aggregate Metrics -- **Client-side detection limitations**: JavaScript-based property detection may not identify all placements -- **Identifier type mismatch**: The agent specializes in domains but receives app store IDs -- **New properties**: Recently added properties not yet in the agent's database +Governance agents can optionally return computed metrics in the `aggregate` field: + +```json +"aggregate": { + "score": 68.7, + "grade": "C+", + "label": "68.7% compliant", + "methodology_url": "https://governance.example.com/methodology" +} +``` + +| Field | Description | +|-------|-------------| +| `score` | Numeric score (scale is agent-defined, typically 0-100) | +| `grade` | Letter grade or category (e.g., "A+", "B-", "Gold") | +| `label` | Human-readable summary (e.g., "85% compliant") | +| `methodology_url` | URL explaining how the aggregate was calculated | -**Unknown records are valid** - you cannot penalize for detection gaps. +The `aggregate` field is optional and agent-specific. Consumers should not assume a particular format - always check `methodology_url` for interpretation. -## Calculating Rates +## Calculating Your Own Rates -The response returns raw counts. Calculate rates as needed: +The response always includes raw counts. Calculate rates as needed: ```python -# Compliance rate (exclude unknowns from denominator) -verifiable = summary.total_impressions - summary.unknown_impressions +# Compliance rate (exclude unverifiable from denominator) +unverifiable = summary.unmodeled_impressions + summary.unidentified_impressions +verifiable = summary.total_impressions - unverifiable compliance_rate = summary.compliant_impressions / verifiable if verifiable > 0 else None ``` In the example above: - Compliant impressions: 103 - Non-compliant impressions: 47 -- Unknown impressions: 25 (excluded) -- Compliance rate: 103 / (175 - 25) = 103 / 150 = 68.7% +- Unmodeled + Unidentified impressions: 50 (excluded) +- Compliance rate: 103 / (200 - 50) = 103 / 150 = 68.7% ## Violation Codes diff --git a/static/schemas/source/property/validate-property-delivery-response.json b/static/schemas/source/property/validate-property-delivery-response.json index f5c9b74af4..a6bd193cd1 100644 --- a/static/schemas/source/property/validate-property-delivery-response.json +++ b/static/schemas/source/property/validate-property-delivery-response.json @@ -37,13 +37,21 @@ "type": "integer", "description": "Impressions from non_compliant records" }, - "unknown_records": { + "unmodeled_records": { "type": "integer", - "description": "Number of records with unknown status (identifier not resolvable)" + "description": "Number of records where identifier was recognized but no data available" }, - "unknown_impressions": { + "unmodeled_impressions": { + "type": "integer", + "description": "Impressions from unmodeled records" + }, + "unidentified_records": { "type": "integer", - "description": "Impressions from unknown records" + "description": "Number of records where identifier type was not resolvable" + }, + "unidentified_impressions": { + "type": "integer", + "description": "Impressions from unidentified records" } }, "required": [ @@ -53,11 +61,37 @@ "compliant_impressions", "non_compliant_records", "non_compliant_impressions", - "unknown_records", - "unknown_impressions" + "unmodeled_records", + "unmodeled_impressions", + "unidentified_records", + "unidentified_impressions" ], "additionalProperties": false }, + "aggregate": { + "type": "object", + "description": "Optional aggregate measurements computed by the governance agent. Format and meaning are agent-specific.", + "properties": { + "score": { + "type": "number", + "description": "Numeric score (0-100 scale typical, but agent-defined)" + }, + "grade": { + "type": "string", + "description": "Letter grade or category (e.g., 'A+', 'B-', 'Gold', 'Compliant')" + }, + "label": { + "type": "string", + "description": "Human-readable summary (e.g., '85% compliant', 'High quality')" + }, + "methodology_url": { + "type": "string", + "format": "uri", + "description": "URL explaining how this aggregate was calculated" + } + }, + "additionalProperties": true + }, "authorization_summary": { "type": "object", "description": "Aggregate authorization statistics. Only present if any records included sales_agent_url.", diff --git a/static/schemas/source/property/validation-result.json b/static/schemas/source/property/validation-result.json index 83ef11721e..cb93ec1042 100644 --- a/static/schemas/source/property/validation-result.json +++ b/static/schemas/source/property/validation-result.json @@ -15,8 +15,8 @@ }, "status": { "type": "string", - "enum": ["compliant", "non_compliant", "unknown"], - "description": "Validation status: compliant (in list), non_compliant (not in list), unknown (identifier type not resolvable by this governance agent)" + "enum": ["compliant", "non_compliant", "unmodeled", "unidentified"], + "description": "Validation status: compliant (in list), non_compliant (not in list), unmodeled (identifier recognized but no data available), unidentified (identifier type not resolvable by this governance agent)" }, "impressions": { "type": "integer", From 219b94e084b05ebf4dfeacb15f7d994c5d6f9b94 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 14:49:11 -0500 Subject: [PATCH 09/19] Fix include_compliant description to reference new statuses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated the description to list non_compliant, unmodeled, and unidentified (instead of the old "unknown" status). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../source/property/validate-property-delivery-request.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/schemas/source/property/validate-property-delivery-request.json b/static/schemas/source/property/validate-property-delivery-request.json index 491e8c3ac8..16597cf433 100644 --- a/static/schemas/source/property/validate-property-delivery-request.json +++ b/static/schemas/source/property/validate-property-delivery-request.json @@ -21,7 +21,7 @@ "include_compliant": { "type": "boolean", "default": false, - "description": "Include compliant records in results (default: only return non_compliant and unknown)" + "description": "Include compliant records in results (default: only return non_compliant, unmodeled, and unidentified)" }, "ext": { "$ref": "/schemas/core/ext.json" From 52b7b0041f245dab72ca902d4c24814939424981 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 15:10:06 -0500 Subject: [PATCH 10/19] Move authorized-properties doc to Governance Protocol section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Moved authorized-properties.mdx from media-buy/capability-discovery to governance/property (renamed to "Understanding Authorization") - Added validate_property_delivery task to navigation - Navigation now shows conceptual explainer before tech spec 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs.json | 4 +++- .../property}/authorized-properties.mdx | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) rename docs/{media-buy/capability-discovery => governance/property}/authorized-properties.mdx (99%) diff --git a/docs.json b/docs.json index 2ca913be1b..1344d4b14e 100644 --- a/docs.json +++ b/docs.json @@ -103,13 +103,15 @@ "pages": [ "docs/governance/property/index", "docs/governance/property/specification", + "docs/governance/property/authorized-properties", "docs/governance/property/adagents", { "group": "Tasks", "pages": [ "docs/governance/property/tasks/index", "docs/governance/property/tasks/list_property_features", - "docs/governance/property/tasks/property_lists" + "docs/governance/property/tasks/property_lists", + "docs/governance/property/tasks/validate_property_delivery" ] } ] diff --git a/docs/media-buy/capability-discovery/authorized-properties.mdx b/docs/governance/property/authorized-properties.mdx similarity index 99% rename from docs/media-buy/capability-discovery/authorized-properties.mdx rename to docs/governance/property/authorized-properties.mdx index 13264accab..37981ec9e5 100644 --- a/docs/media-buy/capability-discovery/authorized-properties.mdx +++ b/docs/governance/property/authorized-properties.mdx @@ -1,5 +1,6 @@ --- -title: Authorized Properties +sidebar_position: 2.5 +title: Understanding Authorization description: Understanding property authorization in AdCP - preventing unauthorized resale and validating sales agent relationships keywords: [authorized properties, adagents.json, unauthorized resale, ads.txt, sales agent authorization, property validation] --- From b861f70e9ce935058d025516a8f7fc7e217e48ce Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 15:15:00 -0500 Subject: [PATCH 11/19] Fix broken links to authorized-properties after move MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated links in media-buy docs to point to new location in governance/property section. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/media-buy/capability-discovery/index.mdx | 2 +- docs/media-buy/index.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/media-buy/capability-discovery/index.mdx b/docs/media-buy/capability-discovery/index.mdx index 76091f5049..297984b4c1 100644 --- a/docs/media-buy/capability-discovery/index.mdx +++ b/docs/media-buy/capability-discovery/index.mdx @@ -21,7 +21,7 @@ Learn how sales agents can support standard creative formats through the referen - Leverage the Standard Creative Agent for standard formats - Work with publisher-specific creative agents for custom formats -### [Authorized Properties](/docs/media-buy/capability-discovery/authorized-properties) 🔐 +### [Understanding Authorization](/docs/governance/property/authorized-properties) 🔐 Learn how AdCP prevents unauthorized resale and ensures sales agents are legitimate. Understand: - The problem of unauthorized resale in digital advertising diff --git a/docs/media-buy/index.mdx b/docs/media-buy/index.mdx index 0ead41042a..ab06863c28 100644 --- a/docs/media-buy/index.mdx +++ b/docs/media-buy/index.mdx @@ -232,7 +232,7 @@ Choose your path based on your role and needs: 4. **Explore [Optimization & Reporting](/docs/media-buy/media-buys/optimization-reporting)** - Learn performance management ### **For Publishers/Sales Agents** -1. **Learn [Authorized Properties](/docs/media-buy/capability-discovery/authorized-properties)** - Understand authorization requirements +1. **Learn [Understanding Authorization](/docs/governance/property/authorized-properties)** - Understand authorization requirements 2. **Review [Creative Formats](/docs/creative/formats)** - See supported creative specifications 3. **Study [Advanced Topics](/docs/media-buy/advanced-topics/)** - Deep dive into technical implementation From 35af98b9d98188a78d12efc2cca6074917459338 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 16:14:41 -0500 Subject: [PATCH 12/19] Add property_list filtering to get_products for 2.6.x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add optional property_list parameter to get_products request schema - Add property_list_applied response field to indicate filtering status - Update get_products docs with new parameter, response field, and usage example - Mark changeset as minor for 2.6.x release 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .changeset/property-governance-protocol.md | 7 +- .../media-buy/task-reference/get_products.mdx | 81 +++++++++++++++++++ .../media-buy/get-products-request.json | 4 + .../media-buy/get-products-response.json | 4 + 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/.changeset/property-governance-protocol.md b/.changeset/property-governance-protocol.md index d3f767619c..882945edce 100644 --- a/.changeset/property-governance-protocol.md +++ b/.changeset/property-governance-protocol.md @@ -1,6 +1,9 @@ --- +"adcontextprotocol": minor --- -[DRAFT] Property Governance Protocol for AdCP 3.0 +Add Property Governance Protocol support to get_products -This is a proposal/draft specification that does not yet affect the released protocol version. No version bump required. +- Add optional `property_list` parameter to get_products request for filtering products by property list +- Add `property_list_applied` response field to indicate whether filtering was applied +- Enables buyers to pass property lists from governance agents to sales agents for compliant inventory discovery diff --git a/docs/media-buy/task-reference/get_products.mdx b/docs/media-buy/task-reference/get_products.mdx index 9a877d146e..fa00a940b7 100644 --- a/docs/media-buy/task-reference/get_products.mdx +++ b/docs/media-buy/task-reference/get_products.mdx @@ -129,6 +129,7 @@ asyncio.run(discover_with_filters()) | `brief` | string | No | Natural language description of campaign requirements | | `brand_manifest` | BrandManifest \| string | No | Brand information (inline object or URL). See [Brand Manifest](/docs/creative/brand-manifest) | | `filters` | Filters | No | Structured filters (see below) | +| `property_list` | PropertyListRef | No | [AdCP 3.0] Reference to a property list for filtering. See [Property Lists](/docs/governance/property/tasks/property_lists) | ### Filters Object @@ -172,6 +173,12 @@ Returns an array of `products`, each containing: | `pricing_options` | PricingOption[] | Available pricing models (CPM, CPCV, etc.) | | `brief_relevance` | string | Why this product matches the brief (when brief provided) | +### Response Metadata + +| Field | Type | Description | +|-------|------|-------------| +| `property_list_applied` | boolean | [AdCP 3.0] `true` if the agent filtered products based on the provided `property_list`. Absent or `false` if not provided or not supported. | + **See schema for complete field list**: [`get-products-response.json`](https://adcontextprotocol.org/schemas/v2/media-buy/get-products-response.json) ## Common Scenarios @@ -476,6 +483,80 @@ asyncio.run(discover_standard_formats()) +### Property List Filtering + + +**AdCP 3.0** - Property list filtering requires governance agent support. + + +Filter products to only those available on properties in your approved list: + + + +```javascript JavaScript +import { testAgent } from '@adcp/client/testing'; + +// Filter products by property list from governance agent +const result = await testAgent.getProducts({ + brief: 'Brand-safe inventory for family brand', + brand_manifest: { + name: 'Nike', + url: 'https://nike.com' + }, + property_list: { + agent_url: 'https://governance.example.com', + list_id: 'pl_brand_safe_2024' + } +}); + +if (result.success && result.data) { + // Check if filtering was actually applied + if (result.data.property_list_applied) { + console.log(`Found ${result.data.products.length} products on approved properties`); + } else { + console.log('Agent does not support property list filtering'); + console.log(`Found ${result.data.products.length} products (unfiltered)`); + } +} +``` + +```python Python +import asyncio +from adcp import test_agent + +async def discover_with_property_list(): + # Filter products by property list from governance agent + result = await test_agent.simple.get_products( + brief='Brand-safe inventory for family brand', + brand_manifest={ + 'name': 'Nike', + 'url': 'https://nike.com' + }, + property_list={ + 'agent_url': 'https://governance.example.com', + 'list_id': 'pl_brand_safe_2024' + } + ) + + # Check if filtering was actually applied + if result.get('property_list_applied'): + print(f"Found {len(result['products'])} products on approved properties") + else: + print("Agent does not support property list filtering") + print(f"Found {len(result['products'])} products (unfiltered)") + +asyncio.run(discover_with_property_list()) +``` + + + +**Note**: If `property_list_applied` is absent or `false`, the sales agent did not filter products. This can happen if: +- The agent doesn't support property governance features +- The agent couldn't access the property list +- The property list had no effect on the available inventory + +See [Property Governance](/docs/governance/property/specification) for more on property lists. + ## Error Handling | Error Code | Description | Resolution | diff --git a/static/schemas/source/media-buy/get-products-request.json b/static/schemas/source/media-buy/get-products-request.json index c946499984..a827fd6c05 100644 --- a/static/schemas/source/media-buy/get-products-request.json +++ b/static/schemas/source/media-buy/get-products-request.json @@ -16,6 +16,10 @@ "filters": { "$ref": "/schemas/core/product-filters.json" }, + "property_list": { + "$ref": "/schemas/core/property-list-ref.json", + "description": "[AdCP 3.0] Reference to an externally managed property list. When provided, the sales agent should filter products to only those available on properties in the list." + }, "context": { "$ref": "/schemas/core/context.json" }, diff --git a/static/schemas/source/media-buy/get-products-response.json b/static/schemas/source/media-buy/get-products-response.json index 849909f20a..f10d57699b 100644 --- a/static/schemas/source/media-buy/get-products-response.json +++ b/static/schemas/source/media-buy/get-products-response.json @@ -19,6 +19,10 @@ "$ref": "/schemas/core/error.json" } }, + "property_list_applied": { + "type": "boolean", + "description": "[AdCP 3.0] Indicates whether property_list filtering was applied. True if the agent filtered products based on the provided property_list. Absent or false if property_list was not provided or not supported by this agent." + }, "context": { "$ref": "/schemas/core/context.json" }, From 7651aa17ae53d22f8dce4db858871138faff9800 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 5 Jan 2026 10:16:19 -0500 Subject: [PATCH 13/19] Add governance overview enhancements for working group alignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change banner from Info to Warning for draft status - Add mission statement for governance protocols - Add Working Group Areas table mapping to protocol domains - Add Prompt-Based Policies section for natural language policy support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/index.mdx | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/docs/governance/index.mdx b/docs/governance/index.mdx index 53137e39e6..f08e368f2d 100644 --- a/docs/governance/index.mdx +++ b/docs/governance/index.mdx @@ -3,12 +3,16 @@ sidebar_position: 1 title: Governance Protocol --- - -**AdCP 3.0 Proposal** - This protocol is under development for AdCP 3.0. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). - + +**Draft for AdCP 3.0** - These governance protocols are under active development by the AAO Governance Working Group. Feedback welcome via [GitHub Discussions](https://github.com/adcontextprotocol/adcp/discussions). + The Governance Protocol provides standardized mechanisms for compliance, brand safety, and quality control across advertising workflows. It enables specialized agents to evaluate, filter, and score advertising entities against configurable criteria. +## Mission + +> Define and operate transparent, privacy-safe, brand-suitable campaign governance that encodes human heuristics in open protocol objects, making campaigns easy to launch, safe to scale, and measurable against verifiable outcomes for buyers, sellers, and auditors. + ## Protocol Domains The Governance Protocol covers four distinct governance domains: @@ -22,6 +26,19 @@ The Governance Protocol covers four distinct governance domains: Each domain has specialized governance agents that provide data, filtering, and scoring capabilities. +## Working Group Areas + +The AAO Governance Working Group is developing standards across six areas that inform these protocols: + +| Area | Focus | Protocol Impact | +|------|-------|-----------------| +| Brand Safety & Suitability | Safe, appropriate ad contexts | Property Governance, Content filtering | +| Brand Manifesto & Standards Library | Machine-readable brand policies | Brand Standards | +| Creative Standards & Validation | Technical and ethical creative criteria | Creative Governance | +| Process & Human Oversight | Human-in-the-loop checkpoints | All protocols | +| Regulatory & Compliance | Legal and regional frameworks | All protocols | +| Measurement & Verification | Proving compliance | All protocols | + ## Architecture Governance agents operate across the **full campaign lifecycle**: @@ -98,6 +115,19 @@ Buyers declare their brand identity, and governance agents automatically apply a When a governance agent sees this manifest, it automatically applies COPPA requirements, content filtering for children's brands, and other industry-specific rules. +### Prompt-Based Policies + +Governance protocols support **natural language prompts** for policy definitions rather than rigid keyword lists: + +```json +{ + "policy": "Avoid content about violence, controversial politics, or adult themes. Sports news is excellent. Entertainment is generally acceptable.", + "exclusions_prompt": "Block content containing hate speech, illegal activities, or ongoing litigation against our company." +} +``` + +This enables AI-powered verification agents to understand context and nuance. + ### Webhook Notifications Governance agents notify subscribers when evaluations change: From 663d4aa2fdb911d2ab1e62c78adc171be37be143 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 8 Jan 2026 06:16:20 -0800 Subject: [PATCH 14/19] Fix broken links in v2.6-rc docs after authorized-properties move MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update links from /docs/media-buy/capability-discovery/adagents and /docs/media-buy/capability-discovery/authorized-properties to their new locations in /docs/governance/property/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../capability-discovery/authorized-properties.mdx | 4 ++-- v2.6-rc/docs/media-buy/capability-discovery/index.mdx | 6 +++--- v2.6-rc/docs/media-buy/index.mdx | 2 +- .../media-buy/task-reference/list_authorized_properties.mdx | 2 +- v2.6-rc/docs/reference/implementor-faq.mdx | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/v2.6-rc/docs/media-buy/capability-discovery/authorized-properties.mdx b/v2.6-rc/docs/media-buy/capability-discovery/authorized-properties.mdx index 11d72b671c..13264accab 100644 --- a/v2.6-rc/docs/media-buy/capability-discovery/authorized-properties.mdx +++ b/v2.6-rc/docs/media-buy/capability-discovery/authorized-properties.mdx @@ -268,11 +268,11 @@ For complete technical details on implementing the `adagents.json` file format, - Validation code examples and error handling - Security considerations and best practices -See the **[adagents.json Tech Spec](/docs/media-buy/capability-discovery/adagents)** for complete implementation guidance. +See the **[adagents.json Tech Spec](/docs/governance/property/adagents)** for complete implementation guidance. ## Related Documentation - **[`list_authorized_properties`](/docs/media-buy/task-reference/list_authorized_properties)** - API reference for property discovery - **[Product Discovery](/docs/media-buy/product-discovery/)** - How authorization integrates with product discovery - **[Properties Schema](https://adcontextprotocol.org/schemas/v2/core/property.json)** - Technical property data model -- **[adagents.json Tech Spec](/docs/media-buy/capability-discovery/adagents)** - Complete `adagents.json` implementation guide \ No newline at end of file +- **[adagents.json Tech Spec](/docs/governance/property/adagents)** - Complete `adagents.json` implementation guide \ No newline at end of file diff --git a/v2.6-rc/docs/media-buy/capability-discovery/index.mdx b/v2.6-rc/docs/media-buy/capability-discovery/index.mdx index 885a0e16c9..214806a992 100644 --- a/v2.6-rc/docs/media-buy/capability-discovery/index.mdx +++ b/v2.6-rc/docs/media-buy/capability-discovery/index.mdx @@ -21,7 +21,7 @@ Learn how sales agents can support standard creative formats through the referen - Leverage the Standard Creative Agent for standard formats - Work with publisher-specific creative agents for custom formats -### [Authorized Properties](/docs/media-buy/capability-discovery/authorized-properties) 🔐 +### [Authorized Properties](/docs/governance/property/authorized-properties) 🔐 Learn how AdCP prevents unauthorized resale and ensures sales agents are legitimate. Understand: - The problem of unauthorized resale in digital advertising @@ -30,7 +30,7 @@ Learn how AdCP prevents unauthorized resale and ensures sales agents are legitim - Property tags and large-scale authorization management -**Cross-Protocol Foundation**: The `adagents.json` authorization system applies across ALL AdCP protocols (Media Buy, Signals, and future Curation). See the [adagents.json specification](/docs/media-buy/capability-discovery/adagents) for complete implementation details. +**Cross-Protocol Foundation**: The `adagents.json` authorization system applies across ALL AdCP protocols (Media Buy, Signals, and future Curation). See the [adagents.json specification](/docs/governance/property/adagents) for complete implementation details. @@ -80,4 +80,4 @@ Together, these capabilities provide the foundation for safe, efficient, and eff - **[Creative Protocol](/docs/creative/)** - Creative agents and manifests - **[Creative Channel Guides](/docs/creative/channels/video)** - Format examples and patterns - **[Creative Manifests](/docs/creative/creative-manifests)** - Understanding creative specifications -- **[adagents.json Specification](/docs/media-buy/capability-discovery/adagents)** - Publisher authorization system (applies across all AdCP protocols) \ No newline at end of file +- **[adagents.json Specification](/docs/governance/property/adagents)** - Publisher authorization system (applies across all AdCP protocols) \ No newline at end of file diff --git a/v2.6-rc/docs/media-buy/index.mdx b/v2.6-rc/docs/media-buy/index.mdx index 0ead41042a..fcaed419b0 100644 --- a/v2.6-rc/docs/media-buy/index.mdx +++ b/v2.6-rc/docs/media-buy/index.mdx @@ -232,7 +232,7 @@ Choose your path based on your role and needs: 4. **Explore [Optimization & Reporting](/docs/media-buy/media-buys/optimization-reporting)** - Learn performance management ### **For Publishers/Sales Agents** -1. **Learn [Authorized Properties](/docs/media-buy/capability-discovery/authorized-properties)** - Understand authorization requirements +1. **Learn [Authorized Properties](/docs/governance/property/authorized-properties)** - Understand authorization requirements 2. **Review [Creative Formats](/docs/creative/formats)** - See supported creative specifications 3. **Study [Advanced Topics](/docs/media-buy/advanced-topics/)** - Deep dive into technical implementation diff --git a/v2.6-rc/docs/media-buy/task-reference/list_authorized_properties.mdx b/v2.6-rc/docs/media-buy/task-reference/list_authorized_properties.mdx index 5e7cb8ea9d..55bd3fcf87 100644 --- a/v2.6-rc/docs/media-buy/task-reference/list_authorized_properties.mdx +++ b/v2.6-rc/docs/media-buy/task-reference/list_authorized_properties.mdx @@ -498,6 +498,6 @@ After discovering authorized properties: ## Learn More -- [adagents.json Specification](/docs/media-buy/capability-discovery/adagents) - Publisher authorization file format +- [adagents.json Specification](/docs/governance/property/adagents) - Publisher authorization file format - [Property Schema](https://adcontextprotocol.org/schemas/v2/core/property.json) - Property definition structure - [Authorization Guide](/docs/reference/authentication) - How authorization works in AdCP diff --git a/v2.6-rc/docs/reference/implementor-faq.mdx b/v2.6-rc/docs/reference/implementor-faq.mdx index ce93b224df..747ada9c0c 100644 --- a/v2.6-rc/docs/reference/implementor-faq.mdx +++ b/v2.6-rc/docs/reference/implementor-faq.mdx @@ -289,7 +289,7 @@ assert(response.message.includes("policy")); 3. Verify the sales agent URL appears in `authorized_agents` 4. Reject products from unauthorized agents -See [Authorization Validation](/docs/media-buy/capability-discovery/adagents#buyer-agent-validation) for complete requirements. +See [Authorization Validation](/docs/governance/property/adagents#buyer-agent-validation) for complete requirements. ## Terminology From cf739d5741be3b820bbca8e9745ae17cdf71429a Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sat, 10 Jan 2026 13:48:07 -0500 Subject: [PATCH 15/19] Add get_property_features task and property_features discovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add get_property_features task schemas for governance agents to return property certification/measurement data (carbon scores, TAG certified, etc.) - Support two request modes: explicit property list or publisher discovery - Add publisher_domain, property_types, property_tags filtering for discovery - Add property_features field to adagents.json for feature provider discovery - Rename from governance_agents to property_features for flexibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- static/schemas/source/adagents.json | 92 +++++++++++++++++++ .../get-property-features-request.json | 68 ++++++++++++++ .../get-property-features-response.json | 28 ++++++ .../property/property-feature-result.json | 43 +++++++++ .../property/property-feature-value.json | 51 ++++++++++ 5 files changed, 282 insertions(+) create mode 100644 static/schemas/source/property/get-property-features-request.json create mode 100644 static/schemas/source/property/get-property-features-response.json create mode 100644 static/schemas/source/property/property-feature-result.json create mode 100644 static/schemas/source/property/property-feature-value.json diff --git a/static/schemas/source/adagents.json b/static/schemas/source/adagents.json index 286aee996f..bc48225ca6 100644 --- a/static/schemas/source/adagents.json +++ b/static/schemas/source/adagents.json @@ -264,6 +264,38 @@ "type": "string", "format": "date-time", "description": "ISO 8601 timestamp indicating when this file was last updated" + }, + "property_features": { + "type": "array", + "description": "[AdCP 3.0] Optional list of agents that provide property feature data (certifications, scores, compliance status). Used for discovery - actual data comes from querying the agent's get_property_features task.", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The agent's API endpoint URL (must implement get_property_features)" + }, + "name": { + "type": "string", + "description": "Human-readable name of the vendor/agent (e.g., 'Scope3', 'TAG', 'OneTrust')" + }, + "features": { + "type": "array", + "description": "Feature IDs this agent provides (e.g., 'carbon_score', 'tag_certified_against_fraud'). Use list_property_features on the agent for full definitions.", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "publisher_id": { + "type": "string", + "description": "Optional publisher identifier at this agent (for lookup)" + } + }, + "required": ["url", "name", "features"], + "additionalProperties": true + } } }, "required": [ @@ -489,6 +521,66 @@ } ], "last_updated": "2025-01-10T17:00:00Z" + }, + { + "$schema": "/schemas/adagents.json", + "contact": { + "name": "Premium News Publisher", + "email": "adops@news.example.com", + "domain": "news.example.com" + }, + "properties": [ + { + "property_type": "website", + "name": "News Example", + "identifiers": [ + { + "type": "domain", + "value": "news.example.com" + } + ], + "tags": ["premium", "news"], + "publisher_domain": "news.example.com" + } + ], + "tags": { + "premium": { + "name": "Premium Properties", + "description": "High-quality, brand-safe properties" + }, + "news": { + "name": "News Properties", + "description": "News and journalism content" + } + }, + "authorized_agents": [ + { + "url": "https://sales.news.example.com", + "authorized_for": "All news properties", + "authorization_type": "property_tags", + "property_tags": ["news"] + } + ], + "property_features": [ + { + "url": "https://api.scope3.com", + "name": "Scope3", + "features": ["carbon_score", "sustainability_grade"], + "publisher_id": "pub_news_12345" + }, + { + "url": "https://api.tagtoday.net", + "name": "TAG", + "features": ["tag_certified_against_fraud", "tag_brand_safety_certified"] + }, + { + "url": "https://api.onetrust.com", + "name": "OneTrust", + "features": ["gdpr_compliant", "tcf_registered", "ccpa_compliant"], + "publisher_id": "ot_news_67890" + } + ], + "last_updated": "2025-01-10T18:00:00Z" } ] } diff --git a/static/schemas/source/property/get-property-features-request.json b/static/schemas/source/property/get-property-features-request.json new file mode 100644 index 0000000000..a664eb65a9 --- /dev/null +++ b/static/schemas/source/property/get-property-features-request.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/get-property-features-request.json", + "title": "Get Property Features Request", + "description": "Request payload for get_property_features task. Retrieves feature values for properties from a governance agent. Supports two modes: explicit property list OR publisher-based discovery.", + "type": "object", + "properties": { + "properties": { + "type": "array", + "description": "Explicit list of properties to get features for. Can be domain strings or structured property references. Use this when you know exactly which properties you want.", + "items": { + "oneOf": [ + { + "type": "string", + "description": "Domain string (e.g., 'example.com')" + }, + { + "$ref": "/schemas/core/property-id.json" + } + ] + }, + "minItems": 1, + "maxItems": 100 + }, + "publisher_domain": { + "type": "string", + "description": "Publisher domain to discover properties for. Use this to get features for all properties belonging to a publisher. Can be combined with property_types and property_tags for filtering." + }, + "property_types": { + "type": "array", + "description": "Filter to specific property types (e.g., 'website', 'mobile_app', 'ctv_app'). Only applies when using publisher_domain.", + "items": { + "$ref": "/schemas/enums/property-type.json" + } + }, + "property_tags": { + "type": "array", + "description": "Filter to properties with these tags. Only applies when using publisher_domain.", + "items": { + "$ref": "/schemas/core/property-tag.json" + } + }, + "feature_ids": { + "type": "array", + "description": "Optional filter to specific features. If omitted, returns all available features.", + "items": { + "type": "string" + } + }, + "context": { + "$ref": "/schemas/core/context.json" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "oneOf": [ + { + "required": ["properties"], + "description": "Explicit property list mode" + }, + { + "required": ["publisher_domain"], + "description": "Publisher discovery mode" + } + ], + "additionalProperties": false +} diff --git a/static/schemas/source/property/get-property-features-response.json b/static/schemas/source/property/get-property-features-response.json new file mode 100644 index 0000000000..d07a0d480d --- /dev/null +++ b/static/schemas/source/property/get-property-features-response.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/get-property-features-response.json", + "title": "Get Property Features Response", + "description": "Response payload for get_property_features task. Returns feature values for requested properties.", + "type": "object", + "properties": { + "results": { + "type": "array", + "description": "Feature values for each requested property", + "items": { + "$ref": "/schemas/property/property-feature-result.json" + } + }, + "errors": { + "type": "array", + "description": "Errors for properties that could not be processed", + "items": { + "$ref": "/schemas/core/error.json" + } + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["results"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-feature-result.json b/static/schemas/source/property/property-feature-result.json new file mode 100644 index 0000000000..25a43a6439 --- /dev/null +++ b/static/schemas/source/property/property-feature-result.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-feature-result.json", + "title": "Property Feature Result", + "description": "Feature values for a single property from a governance agent.", + "type": "object", + "properties": { + "property": { + "oneOf": [ + { + "type": "string", + "description": "Domain string (e.g., 'example.com')" + }, + { + "$ref": "/schemas/core/property-id.json" + } + ], + "description": "The property these features apply to" + }, + "features": { + "type": "object", + "description": "Map of feature_id to feature value", + "additionalProperties": { + "$ref": "/schemas/property/property-feature-value.json" + } + }, + "coverage_status": { + "type": "string", + "enum": ["covered", "not_covered", "pending"], + "description": "Whether this property is covered by this governance agent: covered (has data), not_covered (not measured), pending (measurement in progress)" + }, + "last_evaluated": { + "type": "string", + "format": "date-time", + "description": "When features were last evaluated for this property" + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["property", "coverage_status"], + "additionalProperties": false +} diff --git a/static/schemas/source/property/property-feature-value.json b/static/schemas/source/property/property-feature-value.json new file mode 100644 index 0000000000..c2b70ab712 --- /dev/null +++ b/static/schemas/source/property/property-feature-value.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/property/property-feature-value.json", + "title": "Property Feature Value", + "description": "A single feature value for a property. Structure varies by feature type (binary, quantitative, categorical).", + "type": "object", + "properties": { + "value": { + "description": "The feature value. Type depends on feature definition: boolean for binary, number for quantitative, string for categorical.", + "oneOf": [ + { "type": "boolean" }, + { "type": "number" }, + { "type": "string" } + ] + }, + "unit": { + "type": "string", + "description": "Unit of measurement for quantitative values (e.g., 'gCO2e/1000_impressions', 'percentage')" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Confidence score for this value (0-1)" + }, + "measured_at": { + "type": "string", + "format": "date-time", + "description": "When this specific value was measured" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When this certification/value expires (for time-limited certifications)" + }, + "methodology_version": { + "type": "string", + "description": "Version of the methodology used to calculate this value" + }, + "details": { + "type": "object", + "description": "Additional vendor-specific details about this measurement", + "additionalProperties": true + }, + "ext": { + "$ref": "/schemas/core/ext.json" + } + }, + "required": ["value"], + "additionalProperties": false +} From 7d6dafbbccff8808a02a0a9455633ebccdac0b8b Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 12 Jan 2026 08:15:03 -0500 Subject: [PATCH 16/19] Add filter semantics for geographic and channel compliance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clarifies what "compliant in a country" means for property list filters: - Refers to user location (where impressions are delivered) - NOT publisher headquarters, server hosting, or incorporation country - Requires regulatory compliance, ad delivery capability, and valid consent Also clarifies channel filter semantics (technical capability, availability, quality standards) and adds cross-references between docs and schema. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/property/specification.mdx | 74 +++++++++++++++++++ .../property/tasks/property_lists.mdx | 4 + .../property/property-list-filters.json | 8 +- 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/docs/governance/property/specification.mdx b/docs/governance/property/specification.mdx index df7385773b..479d12f581 100644 --- a/docs/governance/property/specification.mdx +++ b/docs/governance/property/specification.mdx @@ -208,6 +208,8 @@ See the [base-property-source schema](https://adcontextprotocol.org/schemas/v2/p - `channels_any`: Property must support **ANY** of the listed channels - `feature_requirements`: Property must pass **ALL** requirements (AND) +See [Filter Semantics](#filter-semantics) for how geographic and channel filters are interpreted. + **Use Cases**: - Define compliant property sets with filters (country, channel, feature thresholds) - Provide brand manifest for automatic rule application @@ -270,6 +272,78 @@ Performs two independent validations: **Response Format**: The response returns raw counts (compliant, non_compliant, unmodeled, unidentified impressions). Consumers calculate rates as needed. Governance agents may optionally include an `aggregate` field with computed metrics (score, grade, label) - the format and meaning are agent-specific. +## Filter Semantics + +Property list filters use declarative terms (`countries_all`, `channels_any`) that governance agents must interpret consistently. This section defines the semantic meaning of these filters. + +### Geographic Filters (`countries_all`) + +When a property list specifies `countries_all: ["UK", "DE"]`, the governance agent must evaluate whether ads can be **legally delivered to users** in those countries on that property. + +**Definition**: A property is "compliant in country X" if: +1. **Regulatory compliance**: The property meets applicable advertising regulations for that jurisdiction (e.g., GDPR in EU, HFSS restrictions in UK, CCPA in California) +2. **Ad delivery capability**: Ads can be technically served to users located in that country +3. **Consent validity**: Required consent mechanisms are in place for that jurisdiction + +**What "country" means**: +- The **user's location** (where ad impressions are delivered), NOT: + - Publisher headquarters location + - Server hosting location + - Company incorporation country + - Primary audience demographics + +**Example**: A US-headquartered publisher with servers in Ireland can be "compliant in UK" if: +- Their consent mechanism meets UK GDPR requirements +- Their ad content complies with UK advertising standards +- They can serve ads to UK users + +**Multi-country semantics**: `countries_all: ["UK", "DE"]` means the property must pass compliance checks for BOTH jurisdictions independently. This matters because regulations differ: +- A sugary drink ad legal in France may violate UK HFSS rules +- A gambling ad legal in UK may violate German interstate gambling treaty + +### Channel Filters (`channels_any`) + +When a property list specifies `channels_any: ["display", "video"]`, the governance agent evaluates whether the property supports those ad formats. + +**Definition**: A property "supports channel X" if: +1. **Technical capability**: The property can render that ad format +2. **Inventory availability**: That format is offered for sale +3. **Quality standards**: The format meets minimum quality thresholds (e.g., viewability) + +**Supported channels**: `display`, `video`, `audio`, `native`, `ctv`, `dooh` + +**ANY semantics**: A property passes if it supports **at least one** of the specified channels. Use multiple channels when the campaign is flexible about format. + +### Property Type Filters (`property_types`) + +When a property list specifies `property_types: ["website", "mobile_app"]`, the governance agent filters to those property categories. + +**Property types**: `website`, `mobile_app`, `ctv_app`, `dooh`, `podcast`, `radio`, `streaming_audio` + +**Definition**: Property type is determined by the property's primary platform/medium, as declared in its `property_type` field. + +### Feature Requirements (`feature_requirements`) + +Feature-based filters reference capabilities discovered via `list_property_features`. Each governance agent defines its own features with documented semantics. + +**Agent-specific**: Feature meanings are defined by each agent's methodology. The specification does not mandate universal feature definitions - agents compete on the quality and coverage of their features. + +**Buyer responsibility**: Before using a feature filter, buyers should: +1. Call `list_property_features` to understand available features +2. Review the agent's methodology documentation +3. Set appropriate thresholds based on their requirements + +### Implementation Notes for Governance Agents + +Governance agents implementing property list filters MUST: + +1. **Document their interpretation**: Publish clear definitions of how they evaluate geographic compliance +2. **Be consistent**: Apply the same interpretation across all properties +3. **Handle edge cases**: Define behavior for properties with mixed signals (e.g., serves multiple countries) +4. **Expose methodology**: Allow buyers to understand the basis for compliance decisions + +Agents MAY provide additional granularity beyond this specification (e.g., distinguishing between "can deliver to UK users" vs "optimized for UK market") via custom features. + ## Typical Flows ### Property List Flow diff --git a/docs/governance/property/tasks/property_lists.mdx b/docs/governance/property/tasks/property_lists.mdx index c6c19976fb..3af77f749f 100644 --- a/docs/governance/property/tasks/property_lists.mdx +++ b/docs/governance/property/tasks/property_lists.mdx @@ -174,6 +174,10 @@ Filters are applied when the list is resolved (via `get_property_list`): | `feature_requirements` | FeatureRequirement[] | Requirements based on agent-provided features | | `exclude_identifiers` | Identifier[] | Identifiers to always exclude | + +For detailed definitions of what "compliant in a country" and "supports a channel" mean, see [Filter Semantics](/docs/governance/property/specification#filter-semantics) in the Protocol Specification. + + ### Feature Requirements Feature requirements reference features discovered via `list_property_features`. Each agent exposes different features (consent_quality, carbon_score, coppa_certified, etc.). diff --git a/static/schemas/source/property/property-list-filters.json b/static/schemas/source/property/property-list-filters.json index 2e95d6c737..3b2571f26a 100644 --- a/static/schemas/source/property/property-list-filters.json +++ b/static/schemas/source/property/property-list-filters.json @@ -2,12 +2,12 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/property/property-list-filters.json", "title": "Property List Filters", - "description": "Filters that dynamically modify a property list when resolved", + "description": "Filters that dynamically modify a property list when resolved. See Protocol Specification for semantic definitions of geographic and channel compliance.", "type": "object", "properties": { "countries_all": { "type": "array", - "description": "Property must be compliant in ALL listed countries (ISO codes). Required.", + "description": "Property must be compliant for ad delivery to users in ALL listed countries (ISO 3166-1 alpha-2). 'Compliant' means: meets advertising regulations for that jurisdiction, can serve ads to users there, and has required consent mechanisms. Country refers to user location, not publisher headquarters or hosting. Required.", "items": { "type": "string", "pattern": "^[A-Z]{2}$" @@ -15,14 +15,14 @@ }, "channels_any": { "type": "array", - "description": "Property must support ANY of the listed channels. Required.", + "description": "Property must support ANY of the listed channels. 'Supports' means: can render the format, offers it for sale, and meets quality thresholds. Required.", "items": { "$ref": "/schemas/enums/channels.json" } }, "property_types": { "type": "array", - "description": "Filter to these property types", + "description": "Filter to these property types. Type is determined by the property's primary platform/medium.", "items": { "$ref": "/schemas/enums/property-type.json" } From 84557891b12b590bae319f38bad30e35b4632067 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 12 Jan 2026 08:19:28 -0500 Subject: [PATCH 17/19] Revert filter semantics changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Filter Semantics section overstated the meaning of countries_all - it's about whether the agent has feature data for properties in that country, not about legal compliance. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/property/specification.mdx | 74 ------------------- .../property/tasks/property_lists.mdx | 4 - .../property/property-list-filters.json | 8 +- 3 files changed, 4 insertions(+), 82 deletions(-) diff --git a/docs/governance/property/specification.mdx b/docs/governance/property/specification.mdx index 479d12f581..df7385773b 100644 --- a/docs/governance/property/specification.mdx +++ b/docs/governance/property/specification.mdx @@ -208,8 +208,6 @@ See the [base-property-source schema](https://adcontextprotocol.org/schemas/v2/p - `channels_any`: Property must support **ANY** of the listed channels - `feature_requirements`: Property must pass **ALL** requirements (AND) -See [Filter Semantics](#filter-semantics) for how geographic and channel filters are interpreted. - **Use Cases**: - Define compliant property sets with filters (country, channel, feature thresholds) - Provide brand manifest for automatic rule application @@ -272,78 +270,6 @@ Performs two independent validations: **Response Format**: The response returns raw counts (compliant, non_compliant, unmodeled, unidentified impressions). Consumers calculate rates as needed. Governance agents may optionally include an `aggregate` field with computed metrics (score, grade, label) - the format and meaning are agent-specific. -## Filter Semantics - -Property list filters use declarative terms (`countries_all`, `channels_any`) that governance agents must interpret consistently. This section defines the semantic meaning of these filters. - -### Geographic Filters (`countries_all`) - -When a property list specifies `countries_all: ["UK", "DE"]`, the governance agent must evaluate whether ads can be **legally delivered to users** in those countries on that property. - -**Definition**: A property is "compliant in country X" if: -1. **Regulatory compliance**: The property meets applicable advertising regulations for that jurisdiction (e.g., GDPR in EU, HFSS restrictions in UK, CCPA in California) -2. **Ad delivery capability**: Ads can be technically served to users located in that country -3. **Consent validity**: Required consent mechanisms are in place for that jurisdiction - -**What "country" means**: -- The **user's location** (where ad impressions are delivered), NOT: - - Publisher headquarters location - - Server hosting location - - Company incorporation country - - Primary audience demographics - -**Example**: A US-headquartered publisher with servers in Ireland can be "compliant in UK" if: -- Their consent mechanism meets UK GDPR requirements -- Their ad content complies with UK advertising standards -- They can serve ads to UK users - -**Multi-country semantics**: `countries_all: ["UK", "DE"]` means the property must pass compliance checks for BOTH jurisdictions independently. This matters because regulations differ: -- A sugary drink ad legal in France may violate UK HFSS rules -- A gambling ad legal in UK may violate German interstate gambling treaty - -### Channel Filters (`channels_any`) - -When a property list specifies `channels_any: ["display", "video"]`, the governance agent evaluates whether the property supports those ad formats. - -**Definition**: A property "supports channel X" if: -1. **Technical capability**: The property can render that ad format -2. **Inventory availability**: That format is offered for sale -3. **Quality standards**: The format meets minimum quality thresholds (e.g., viewability) - -**Supported channels**: `display`, `video`, `audio`, `native`, `ctv`, `dooh` - -**ANY semantics**: A property passes if it supports **at least one** of the specified channels. Use multiple channels when the campaign is flexible about format. - -### Property Type Filters (`property_types`) - -When a property list specifies `property_types: ["website", "mobile_app"]`, the governance agent filters to those property categories. - -**Property types**: `website`, `mobile_app`, `ctv_app`, `dooh`, `podcast`, `radio`, `streaming_audio` - -**Definition**: Property type is determined by the property's primary platform/medium, as declared in its `property_type` field. - -### Feature Requirements (`feature_requirements`) - -Feature-based filters reference capabilities discovered via `list_property_features`. Each governance agent defines its own features with documented semantics. - -**Agent-specific**: Feature meanings are defined by each agent's methodology. The specification does not mandate universal feature definitions - agents compete on the quality and coverage of their features. - -**Buyer responsibility**: Before using a feature filter, buyers should: -1. Call `list_property_features` to understand available features -2. Review the agent's methodology documentation -3. Set appropriate thresholds based on their requirements - -### Implementation Notes for Governance Agents - -Governance agents implementing property list filters MUST: - -1. **Document their interpretation**: Publish clear definitions of how they evaluate geographic compliance -2. **Be consistent**: Apply the same interpretation across all properties -3. **Handle edge cases**: Define behavior for properties with mixed signals (e.g., serves multiple countries) -4. **Expose methodology**: Allow buyers to understand the basis for compliance decisions - -Agents MAY provide additional granularity beyond this specification (e.g., distinguishing between "can deliver to UK users" vs "optimized for UK market") via custom features. - ## Typical Flows ### Property List Flow diff --git a/docs/governance/property/tasks/property_lists.mdx b/docs/governance/property/tasks/property_lists.mdx index 3af77f749f..c6c19976fb 100644 --- a/docs/governance/property/tasks/property_lists.mdx +++ b/docs/governance/property/tasks/property_lists.mdx @@ -174,10 +174,6 @@ Filters are applied when the list is resolved (via `get_property_list`): | `feature_requirements` | FeatureRequirement[] | Requirements based on agent-provided features | | `exclude_identifiers` | Identifier[] | Identifiers to always exclude | - -For detailed definitions of what "compliant in a country" and "supports a channel" mean, see [Filter Semantics](/docs/governance/property/specification#filter-semantics) in the Protocol Specification. - - ### Feature Requirements Feature requirements reference features discovered via `list_property_features`. Each agent exposes different features (consent_quality, carbon_score, coppa_certified, etc.). diff --git a/static/schemas/source/property/property-list-filters.json b/static/schemas/source/property/property-list-filters.json index 3b2571f26a..2e95d6c737 100644 --- a/static/schemas/source/property/property-list-filters.json +++ b/static/schemas/source/property/property-list-filters.json @@ -2,12 +2,12 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/property/property-list-filters.json", "title": "Property List Filters", - "description": "Filters that dynamically modify a property list when resolved. See Protocol Specification for semantic definitions of geographic and channel compliance.", + "description": "Filters that dynamically modify a property list when resolved", "type": "object", "properties": { "countries_all": { "type": "array", - "description": "Property must be compliant for ad delivery to users in ALL listed countries (ISO 3166-1 alpha-2). 'Compliant' means: meets advertising regulations for that jurisdiction, can serve ads to users there, and has required consent mechanisms. Country refers to user location, not publisher headquarters or hosting. Required.", + "description": "Property must be compliant in ALL listed countries (ISO codes). Required.", "items": { "type": "string", "pattern": "^[A-Z]{2}$" @@ -15,14 +15,14 @@ }, "channels_any": { "type": "array", - "description": "Property must support ANY of the listed channels. 'Supports' means: can render the format, offers it for sale, and meets quality thresholds. Required.", + "description": "Property must support ANY of the listed channels. Required.", "items": { "$ref": "/schemas/enums/channels.json" } }, "property_types": { "type": "array", - "description": "Filter to these property types. Type is determined by the property's primary platform/medium.", + "description": "Filter to these property types", "items": { "$ref": "/schemas/enums/property-type.json" } From b91b8cc4117a1b9241b19541721d2eb9a66c8752 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 12 Jan 2026 08:59:35 -0500 Subject: [PATCH 18/19] Add if_not_covered handling and standardize on 'covered' terminology MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add if_not_covered field to feature-requirement.json (exclude|include) - Add coverage_gaps to get-property-list-response.json for transparency - Change 'unmodeled' to 'not_covered' throughout for consistency: - validation-result.json status enum - validate-property-delivery-response.json summary fields - specification.mdx and validate_property_delivery.mdx documentation - Document coverage handling in property_lists.mdx This allows callers to specify how missing feature data should be handled (strict vs lenient enforcement) and provides visibility into which properties were included despite coverage gaps. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/property/specification.mdx | 6 ++-- .../property/tasks/property_lists.mdx | 33 +++++++++++++++++++ .../tasks/validate_property_delivery.mdx | 20 +++++------ .../source/property/feature-requirement.json | 6 ++++ .../property/get-property-list-response.json | 10 ++++++ .../validate-property-delivery-response.json | 10 +++--- .../source/property/validation-result.json | 4 +-- 7 files changed, 69 insertions(+), 20 deletions(-) diff --git a/docs/governance/property/specification.mdx b/docs/governance/property/specification.mdx index df7385773b..487154ef72 100644 --- a/docs/governance/property/specification.mdx +++ b/docs/governance/property/specification.mdx @@ -258,7 +258,7 @@ Performs two independent validations: **Property Validation Statuses**: - `compliant`: Identifier is in the resolved property list - `non_compliant`: Identifier is NOT in the resolved property list -- `unmodeled`: Identifier recognized but governance agent has no data for it (e.g., property too new) +- `not_covered`: Identifier recognized but governance agent has no data for it (e.g., property too new) - `unidentified`: Identifier type not resolvable by this agent (e.g., detection failed, unsupported type) **Authorization Validation Statuses** (when `sales_agent_url` provided): @@ -266,9 +266,9 @@ Performs two independent validations: - `unauthorized`: Sales agent is NOT in publisher's authorized_agents list - `unknown`: Could not fetch or parse adagents.json -**Unverifiable Records**: Both `unmodeled` and `unidentified` records should be excluded when calculating compliance rates - you cannot penalize for detection gaps or coverage limitations. The distinction helps identify whether the gap is in the agent's data coverage vs the identifier resolution. +**Unverifiable Records**: Both `not_covered` and `unidentified` records should be excluded when calculating compliance rates - you cannot penalize for detection gaps or coverage limitations. The distinction helps identify whether the gap is in the agent's data coverage vs the identifier resolution. -**Response Format**: The response returns raw counts (compliant, non_compliant, unmodeled, unidentified impressions). Consumers calculate rates as needed. Governance agents may optionally include an `aggregate` field with computed metrics (score, grade, label) - the format and meaning are agent-specific. +**Response Format**: The response returns raw counts (compliant, non_compliant, not_covered, unidentified impressions). Consumers calculate rates as needed. Governance agents may optionally include an `aggregate` field with computed metrics (score, grade, label) - the format and meaning are agent-specific. ## Typical Flows diff --git a/docs/governance/property/tasks/property_lists.mdx b/docs/governance/property/tasks/property_lists.mdx index c6c19976fb..23b043201a 100644 --- a/docs/governance/property/tasks/property_lists.mdx +++ b/docs/governance/property/tasks/property_lists.mdx @@ -193,6 +193,39 @@ For **categorical** features (enum values): { "feature_id": "content_category", "allowed_values": ["news", "sports", "technology"] } ``` +### Handling Missing Coverage + +When a property doesn't have data for a required feature, you can control the behavior with `if_not_covered`: + +```json +{ + "feature_id": "viewability_score", + "min_value": 70, + "if_not_covered": "include" +} +``` + +| Value | Behavior | Use Case | +|-------|----------|----------| +| `exclude` (default) | Property is removed from the list | Strict enforcement - only include properties with verified data | +| `include` | Property passes this requirement | Lenient enforcement - don't penalize for coverage gaps | + +When `if_not_covered: "include"` is used, the response includes a `coverage_gaps` field showing which properties were included despite missing data: + +```json +{ + "identifiers": [...], + "coverage_gaps": { + "viewability_score": [ + { "type": "domain", "value": "app.example.com" }, + { "type": "domain", "value": "ctv.example.com" } + ] + } +} +``` + +This transparency helps agencies distinguish between properties that passed a requirement vs. those that couldn't be evaluated. + ### Required Filters Every property list must include at least: diff --git a/docs/governance/property/tasks/validate_property_delivery.mdx b/docs/governance/property/tasks/validate_property_delivery.mdx index 0b0a8801d3..7c7a8a272e 100644 --- a/docs/governance/property/tasks/validate_property_delivery.mdx +++ b/docs/governance/property/tasks/validate_property_delivery.mdx @@ -69,8 +69,8 @@ Validates delivery records against a property list to determine compliance. Answ "compliant_impressions": 103, "non_compliant_records": 1, "non_compliant_impressions": 47, - "unmodeled_records": 1, - "unmodeled_impressions": 25, + "not_covered_records": 1, + "not_covered_impressions": 25, "unidentified_records": 1, "unidentified_impressions": 25 }, @@ -94,7 +94,7 @@ Validates delivery records against a property list to determine compliance. Answ }, { "identifier": { "type": "domain", "value": "new-site.example" }, - "status": "unmodeled", + "status": "not_covered", "impressions": 25 }, { @@ -129,7 +129,7 @@ The summary provides raw counts - consumers calculate rates as needed: | `total_impressions` | Total impressions across all records | | `compliant_records` / `compliant_impressions` | Records/impressions in the property list | | `non_compliant_records` / `non_compliant_impressions` | Records/impressions NOT in the property list | -| `unmodeled_records` / `unmodeled_impressions` | Identifier recognized but no data available | +| `not_covered_records` / `not_covered_impressions` | Identifier recognized but no data available | | `unidentified_records` / `unidentified_impressions` | Identifier type not resolvable | ### Validation Statuses @@ -138,16 +138,16 @@ The summary provides raw counts - consumers calculate rates as needed: |--------|---------| | `compliant` | Identifier is in the resolved property list | | `non_compliant` | Identifier is NOT in the resolved property list | -| `unmodeled` | Identifier recognized but governance agent has no data for it | +| `not_covered` | Identifier recognized but governance agent has no data for it | | `unidentified` | Identifier type not resolvable by this agent | -## Understanding Unmodeled vs Unidentified +## Understanding not_covered vs unidentified These two statuses distinguish different types of "unknown" scenarios: -**`unmodeled`** - The governance agent recognized the identifier (e.g., it's a valid domain) but doesn't have data for that specific property. This happens when: +**`not_covered`** - The governance agent recognized the identifier (e.g., it's a valid domain) but doesn't have data for that specific property. This happens when: - A property is too new to be in the agent's database -- The property exists but hasn't been scored/evaluated yet +- The property exists but hasn't been evaluated yet - The agent's coverage doesn't include that property category **`unidentified`** - The governance agent couldn't recognize the identifier at all. This happens when: @@ -185,7 +185,7 @@ The response always includes raw counts. Calculate rates as needed: ```python # Compliance rate (exclude unverifiable from denominator) -unverifiable = summary.unmodeled_impressions + summary.unidentified_impressions +unverifiable = summary.not_covered_impressions + summary.unidentified_impressions verifiable = summary.total_impressions - unverifiable compliance_rate = summary.compliant_impressions / verifiable if verifiable > 0 else None ``` @@ -193,7 +193,7 @@ compliance_rate = summary.compliant_impressions / verifiable if verifiable > 0 e In the example above: - Compliant impressions: 103 - Non-compliant impressions: 47 -- Unmodeled + Unidentified impressions: 50 (excluded) +- not_covered + unidentified impressions: 50 (excluded) - Compliance rate: 103 / (200 - 50) = 103 / 150 = 68.7% ## Violation Codes diff --git a/static/schemas/source/property/feature-requirement.json b/static/schemas/source/property/feature-requirement.json index 3eae30c929..0a75a1a21f 100644 --- a/static/schemas/source/property/feature-requirement.json +++ b/static/schemas/source/property/feature-requirement.json @@ -21,6 +21,12 @@ "type": "array", "description": "Values that pass the requirement (for binary/categorical features)", "items": {} + }, + "if_not_covered": { + "type": "string", + "enum": ["exclude", "include"], + "default": "exclude", + "description": "How to handle properties where this feature is not covered. 'exclude' (default): property is removed from the list. 'include': property passes this requirement (fail-open)." } }, "required": ["feature_id"], diff --git a/static/schemas/source/property/get-property-list-response.json b/static/schemas/source/property/get-property-list-response.json index f2e90754c1..89ba3bfc5c 100644 --- a/static/schemas/source/property/get-property-list-response.json +++ b/static/schemas/source/property/get-property-list-response.json @@ -49,6 +49,16 @@ "format": "date-time", "description": "Cache expiration timestamp. Re-fetch the list after this time to get updated identifiers." }, + "coverage_gaps": { + "type": "object", + "description": "Properties included in the list despite missing feature data. Only present when a feature_requirement has if_not_covered='include'. Maps feature_id to list of identifiers not covered for that feature.", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "/schemas/core/identifier.json" + } + } + }, "ext": { "$ref": "/schemas/core/ext.json" } diff --git a/static/schemas/source/property/validate-property-delivery-response.json b/static/schemas/source/property/validate-property-delivery-response.json index a6bd193cd1..be229f4067 100644 --- a/static/schemas/source/property/validate-property-delivery-response.json +++ b/static/schemas/source/property/validate-property-delivery-response.json @@ -37,13 +37,13 @@ "type": "integer", "description": "Impressions from non_compliant records" }, - "unmodeled_records": { + "not_covered_records": { "type": "integer", "description": "Number of records where identifier was recognized but no data available" }, - "unmodeled_impressions": { + "not_covered_impressions": { "type": "integer", - "description": "Impressions from unmodeled records" + "description": "Impressions from not_covered records" }, "unidentified_records": { "type": "integer", @@ -61,8 +61,8 @@ "compliant_impressions", "non_compliant_records", "non_compliant_impressions", - "unmodeled_records", - "unmodeled_impressions", + "not_covered_records", + "not_covered_impressions", "unidentified_records", "unidentified_impressions" ], diff --git a/static/schemas/source/property/validation-result.json b/static/schemas/source/property/validation-result.json index cb93ec1042..b11db55aef 100644 --- a/static/schemas/source/property/validation-result.json +++ b/static/schemas/source/property/validation-result.json @@ -15,8 +15,8 @@ }, "status": { "type": "string", - "enum": ["compliant", "non_compliant", "unmodeled", "unidentified"], - "description": "Validation status: compliant (in list), non_compliant (not in list), unmodeled (identifier recognized but no data available), unidentified (identifier type not resolvable by this governance agent)" + "enum": ["compliant", "non_compliant", "not_covered", "unidentified"], + "description": "Validation status: compliant (in list), non_compliant (not in list), not_covered (identifier recognized but no data available), unidentified (identifier type not resolvable by this governance agent)" }, "impressions": { "type": "integer", From 3ea1b2b41e482aa6d0a812653c9feaf4875b4586 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 12 Jan 2026 12:54:04 -0500 Subject: [PATCH 19/19] Clarify countries_all semantics: feature data, not compliance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove "compliant" language from countries_all descriptions to avoid confusion with legal/regulatory compliance. The field means "property must have feature data for these countries" - same semantics as content standards PR 621 ("standards apply in these countries"). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/governance/property/specification.mdx | 2 +- docs/governance/property/tasks/index.mdx | 2 +- docs/governance/property/tasks/property_lists.mdx | 4 ++-- docs/governance/property/tasks/validate_property_delivery.mdx | 2 +- static/schemas/source/property/property-list-filters.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/governance/property/specification.mdx b/docs/governance/property/specification.mdx index 487154ef72..343d1c7e32 100644 --- a/docs/governance/property/specification.mdx +++ b/docs/governance/property/specification.mdx @@ -204,7 +204,7 @@ Create a new property list with filters and optional brand manifest. See the [base-property-source schema](https://adcontextprotocol.org/schemas/v2/property/base-property-source.json) for the full specification. **Filter Logic** (explicit in field names): -- `countries_all`: Property must be compliant in **ALL** listed countries - this is critical because different jurisdictions have different laws +- `countries_all`: Property must have feature data for **ALL** listed countries - `channels_any`: Property must support **ANY** of the listed channels - `feature_requirements`: Property must pass **ALL** requirements (AND) diff --git a/docs/governance/property/tasks/index.mdx b/docs/governance/property/tasks/index.mdx index 92e015acc8..4679e3704b 100644 --- a/docs/governance/property/tasks/index.mdx +++ b/docs/governance/property/tasks/index.mdx @@ -88,7 +88,7 @@ Use `create_property_list` with filters and brand manifest: - **Omitted**: Query the agent's entire property database **Filter logic** (explicit in field names): -- `countries_all`: Property must be compliant in ALL listed countries (different jurisdictions have different laws) +- `countries_all`: Property must have feature data for ALL listed countries - `channels_any`: Property must support ANY of the listed channels - `feature_requirements`: Property must pass ALL requirements (AND) diff --git a/docs/governance/property/tasks/property_lists.mdx b/docs/governance/property/tasks/property_lists.mdx index 23b043201a..a46e54545f 100644 --- a/docs/governance/property/tasks/property_lists.mdx +++ b/docs/governance/property/tasks/property_lists.mdx @@ -168,7 +168,7 @@ Filters are applied when the list is resolved (via `get_property_list`): | Filter | Type | Description | |--------|------|-------------| -| `countries_all` | string[] | ISO country codes - property must be compliant in ALL (required) | +| `countries_all` | string[] | ISO country codes - property must have feature data for ALL (required) | | `channels_any` | string[] | Advertising channels - property must support ANY (required) | | `property_types` | string[] | Property types (website, mobile_app, ctv_app, etc.) | | `feature_requirements` | FeatureRequirement[] | Requirements based on agent-provided features | @@ -238,7 +238,7 @@ These are required because governance agents need to know which jurisdiction and The filter field names make the logic explicit: -- **`countries_all`**: Property must be compliant in **ALL** listed countries. This is critical because different jurisdictions have different laws - a sugary drink ad legal in France may be illegal in the UK. +- **`countries_all`**: Property must have feature data for **ALL** listed countries. - **`channels_any`**: Property must support **ANY** of the listed channels. - **`feature_requirements`**: Property must pass **ALL** requirements (AND). diff --git a/docs/governance/property/tasks/validate_property_delivery.mdx b/docs/governance/property/tasks/validate_property_delivery.mdx index 7c7a8a272e..7f9acc8c42 100644 --- a/docs/governance/property/tasks/validate_property_delivery.mdx +++ b/docs/governance/property/tasks/validate_property_delivery.mdx @@ -202,7 +202,7 @@ In the example above: |------|-------------| | `not_in_list` | Identifier not found in the resolved property list | | `excluded` | Identifier explicitly excluded via `exclude_identifiers` filter | -| `country_mismatch` | Property not compliant in required countries | +| `country_mismatch` | Property lacks feature data for required countries | | `channel_mismatch` | Property doesn't support required channels | | `feature_failed` | Property failed a `feature_requirements` filter | diff --git a/static/schemas/source/property/property-list-filters.json b/static/schemas/source/property/property-list-filters.json index 2e95d6c737..ec132f2bf3 100644 --- a/static/schemas/source/property/property-list-filters.json +++ b/static/schemas/source/property/property-list-filters.json @@ -7,7 +7,7 @@ "properties": { "countries_all": { "type": "array", - "description": "Property must be compliant in ALL listed countries (ISO codes). Required.", + "description": "Property must have feature data for ALL listed countries (ISO codes). Required.", "items": { "type": "string", "pattern": "^[A-Z]{2}$"